diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..399a5294 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +.gradle +/build +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +.DS_Store +node_modules + +# Built application files +*.apk +*.ap_ + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gradle files +.gradle/ +*.iml +/*/*.iml +.idea +/*/.idea/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +/*/.idea + +/buildSdk + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..94ff98a9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing to Tinker +Welcome to [report Issues](https://github.com/Tencent/tinker/issues) or [pull requests](https://github.com/Tencent/tinker/pulls). It's recommended to read the following Contributing Guild first to make contributing earlier. + +## issues +We use Git Issues to track public bugs and feature requests. + +### Search Known Issues First +Please search the exist issues to see if any similar issue or feature request has already been filed. You shold try to make sure your issue doesn't already exist. + +### Reporting New Issues +If you open an issue, the more information the better. Such as detailed description, screenshot or video of your problem, logcat or code blocks for your crash. + +## Pull Requests +We strongly welcome your pull request to make tinker better. + +### Branch Management +There are three main branch here: + +1. `master` branch. + 1. It is the latest (pre-)release branch. We use `master` for tag, with version number `1.1.0`, `1.2.0`, `1.3.0`... + 2. **Don't submit any PR on `master` branch.** +2. `dev` branch. + 1. It is our stable developing branch. After full testing, `dev` will publish to `master` branch for the next release. + 2. **You are recommended to submit bugfix or feature PR on `dev` branch.** +3. `hotfix` branch. + 1. It is the latest tag version for hot fix. If we accept your pull request, we may just tag with version number `1.1.1`, `1.2.3`. + 2. **Only submit urgent PR on `hotfix` branch for next specific release.** + +Normal bugfix or feature request should submit on `dev` branch. After full testing, we will merge them on `master` branch for the next release. + +If you have some urgent bugfix on a published version, but the `master` branch have already far away with the latest tag version, you can submit a PR on hotfix. And it will be cherry picked to `dev` branch if it is possible. + +``` +master + ↑ +dev <--- hotfix PR + ↑ +feature/bugfix PR +``` + +### Make Pull Requests +The code team will monitor all pull request, we run some code check and test on it. After all tests passing, we will accecpt this pr. But it won't merge to `master` branch at once, which have some delay. + +Before submitting a pull request, please make sure the following is done + +1. Fork the repo and create your branch from `master` or `hotfix`. +2. Update code or documentation if you have changed APIs. +3. Add the copyright notice to the top of any new files you've added. +4. Make sure your code lints and checkstyles. +5. Test and test again your code. +6. Now, you can submit your pull request on `dev` or `hotfix` branch. + +## Code Style Guide +Use [Code Style](https://github.com/Tencent/tinker/blob/master/checkstyle.xml) for Java and Android. + +* 4 spaces for indentation rather than tabs + +## License +By contributing to Tinker, you agree that your contributions will be licensed +under its [BSD LICENSE](https://github.com/Tencent/tinker/blob/master/LICENSE) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..255e042d --- /dev/null +++ b/LICENSE @@ -0,0 +1,669 @@ +Tencent is pleased to support the open source community by making Tinker available. + +Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + +If you have downloaded a copy of the Tinker binary from Tencent, please note that the Tinker binary +is licensed under the BSD 3-Clause License. + + +If you have downloaded a copy of the Tinker source code from Tencent, please note that Tinker +source code is licensed under the BSD 3-Clause License, except for the third-party components +listed below which are subject to different license terms. Your integration of Tinker into your own +projects may require compliance with the BSD 3-Clause License, as well as the other licenses +applicable to the third-party components included within Tinker. + +A copy of the BSD 3-Clause License is included in this file. + +Other dependencies and licenses: +---------------------------------------------------------------------------------------- + +Open Source Software Licensed Under the Apache License, Version 2.0: +The below software in this distribution may have been modified by THL A29 Limited (“Tencent +Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. +---------------------------------------------------------------------------------------- +1. Android Source Code 4.4_r1 +Copyright (C) 2005-2015 The Android Open Source Project + +2. buck v2016.04.18.01 +Copyright 2014-present Facebook, Inc. + +3. leakcanary v1.3.1 +Copyright (C) 2014-2015 Square, Inc. + + +Terms of the Apache License, Version 2.0: +--------------------------------------------------- + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by +Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is +granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities that control, are +controlled by, or are under common control with that entity. For the purposes of this definition, +“control” means (i) the power, direct or indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this +License. + +“Source” form shall mean the preferred form for making modifications, including but not limited to +software source code, documentation source, and configuration files. + +“Object” form shall mean any form resulting from mechanical transformation or translation of a +Source form, including but not limited to compiled object code, generated documentation, and +conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made available under +the License, as indicated by a copyright notice that is included in or attached to the work (an +example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or +derived from) the Work and for which the editorial revisions, annotations, elaborations, or other +modifications represent, as a whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version of the Work and +any modifications or additions to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal +Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent to the Licensor or +its representatives, including but not limited to communication on electronic mailing lists, source +code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding communication that is +conspicuously marked or otherwise designated in writing by the copyright owner as “Not a +Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a +Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor +hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, +sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor +hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, +and otherwise transfer the Work, where such license applies only to those patent claims licensable by +such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of +their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute +patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that +the Work or a Contribution incorporated within the Work constitutes direct or contributory patent +infringement, then any patent licenses granted to You under this License for that Work shall +terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, provided that You +meet the following conditions: + + a) You must give any other recipients of the Work or Derivative Works a copy of this License; + and + + b) You must cause any modified files to carry prominent notices stating that You changed the + files;and + + c) You must retain, in the Source form of any Derivative Works that You distribute, all + copyright, patent, trademark, and attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of the Derivative Works;and + + d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative + Works that You distribute must include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not pertain to any part of the + Derivative Works, in at least one of the following places: within a NOTICE text file + distributed as part of the Derivative Works; within the Source form or documentation, if + provided along with the Derivative Works; or, within a display generated by the Derivative + Works, if and wherever such third-party notices normally appear. The contents of the + NOTICE file are for informational purposes only and do not modify the License. You may + add Your own attribution notices within Derivative Works that You distribute, alongside or + as an addendum to the NOTICE text from the Work, provided that such additional attribution + notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or +different license terms and conditions for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the +Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution +intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms +and conditions of this License, without any additional terms or conditions. Notwithstanding the +above, nothing herein shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service +marks, or product names of the Licensor, except as required for reasonable and customary use in +describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor +provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely +responsible for determining the appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including +negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any character arising as a result +of this License or out of the use or inability to use the Work (including but not limited to damages +for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses), even if such Contributor has been advised of the possibility of such +damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works +thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this License. However, in accepting such +obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of +any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor +harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. +---------------------------------------------------------------------------------------- + + +Open Source Software Licensed Under the GNU Lesser General Public License, version 2.1 (LGPL-2.1): +---------------------------------------------------------------------------------------- +1. 7-Zip 16.02 +7-Zip Copyright (C) 1999-2016 Igor Pavlov. + + +Terms of the GNU Lesser General Public License, version 2.1 (LGPL-2.1): +--------------------------------------------------- + +GNU LESSER GENERAL PUBLIC LICENSE +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing +it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU +Library Public License, version 2, hence the version number 2.1.] + + Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By +contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and +change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software +packages--typically libraries--of the Free Software Foundation and other authors who decide to use +it. You can use it too, but we suggest you first think carefully about whether this license or the +ordinary General Public License is the better strategy to use in any particular case, based on the +explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; +that you can change the software and use pieces of it in new free programs; and that you are +nformed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights +or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you +if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the +recipients all the rights that we gave you. You must make sure that they, too, receive or can get the +source code. If you link other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes to the library and +recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you +this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free +library. Also, if the library is modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to +make sure that a company cannot effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a +version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public +License. This license, the GNU Lesser General Public License, applies to certain designated +libraries, and is quite different from the ordinary General Public License. We use this license for +certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the +combination of the two is legally speaking a combined work, a derivative of the original library +The ordinary General Public License therefore permits such linking only if the entire combination +fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking +other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's +freedom than the ordinary General Public License. It also provides other free software developers +Less of an advantage over competing non-free programs. These disadvantages are the reason we use +the ordinary General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a +certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free library does the same job as widely +used non-free libraries. In this case, there is little to gain by limiting the free library to free software +only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number +of people to use a large body of free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU operating system, as well as its +variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure +that the user of a program that is linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close +attention to the difference between a "work based on the library" and a "work that uses the library". +The former contains code derived from the library, whereas the latter must be combined with the library +in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice +placed by the copyright holder or other authorized party saying it may be distributed under the terms +of this Lesser General Public License (also called "this License"). Each licensee is addressed as +you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently +linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under +these terms. A "work based on the Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with +modifications and/or translated straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a +library, complete source code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to control compilation and installation of +the library. + +Activities other than copying, distribution and modification are not covered by this License; they are +outside its scope. The act of running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive +it, in any medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this +License and to the absence of any warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer +warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work +based on the Library, and copy and distribute such modifications or work under the terms of Sectio +1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are +not derived from the Library, and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those sections when you distribute them +as separate works. But when you distribute the same sections as part of a whole which is a work +based on the Library, the distribution of the whole must be on the terms of this License, whose +permissions for other licensees extend to the entire whole, and thus to each and every part regardless +of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely +by you; rather, the intent is to exercise the right to control the distribution of derivative or collective +works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a +work based on the Library) on a volume of a storage or distribution medium does not bring the other +work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this +License to a given copy of the Library. To do this, you must alter all the notices that refer to this +License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this +License. (If a newer version than version 2 of the ordinary GNU General Public License has +appeared, then you can specify that version instead if you wish.) Do not make any other change in +these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU +General Public License applies to all subsequent copies and derivative works made from that copy. +This option is useful when you wish to copy part of the code of the Library into a program that is not +a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in +object code or executable form under the terms of Sections 1 and 2 above provided that you +accompany it with the complete corresponding machine-readable source code, which must be +distributed under the terms of Sections 1 and 2 above on a medium customarily used for software +interchange. + +If distribution of object code is made by offering access to copy from a designated place, then +offering equivalent access to copy the source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not compelled to copy the source along with +the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with +the Library by being compiled or linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this +License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a +derivative of the Library (because it contains portions of the Library), rather than a "work that uses +the library". The executable is therefore covered by this License. Section 6 states terms for +distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the +object code for the work may be a derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be linked without the Library, or if the +work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small +macros and small inline functions (ten lines or less in length), then the use of the object file is +unrestricted, regardless of whether it is legally a derivative work. (Executables containing this +object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work +under the terms of Section 6. Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the +Library" with the Library to produce a work containing portions of the Library, and distribute that +work under terms of your choice, provided that the terms permit modification of the work for the +customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the +Library and its use are covered by this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the copyright notice for the Library +among them, as well as a reference directing the user to the copy of this License. Also, you must do +one of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and +utility programs needed for reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is normally distributed (in either source +or binary form) with the major components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries +that do not normally accompany the operating system. Such a contradiction means you cannot use +both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single +ibrary together with other library facilities not covered by this License, and distribute such a +combined library, provided that the separate distribution of the work based on the Library and of the +other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly +provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under this License will not have +their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else +grants you permission to modify or distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by modifying or distributing the +Library (or any work based on the Library), you indicate your acceptance of this License to do so, +and all its terms and conditions for copying, distributing or modifying the Library or works based on +it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient +automatically receives a license from the original licensor to copy, distribute, link with or modify the +Library subject to these terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by +third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other +reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as a consequence you may +not distribute the Library at all. For example, if a patent license would not permit royalty-free +redistribution of the Library by all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to refrain entirely from distribution +of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the +balance of the section is intended to apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right +claims or to contest validity of any such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software distributed through +that system in reliance on consistent application of that system; it is up to the author/donor to decide +if he or she is willing to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest o + this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or +by copyrighted interfaces, the original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such case, this License +incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of +this License which applies to it and "any later version", you have the option of following the terms +and conditions either of that version or of any later version published by the Free Software +Foundation. If the Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution +conditions are incompatible with these, write to the author to ask for permission. For software which +is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we +sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the +free status of all derivatives of our free software and of promoting the sharing and reuse of software +generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT +WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER +PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS +WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY +MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we +recommend making it free software that everyone can redistribute and change. You can do so +by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General +Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the +start of each source file to most effectively convey 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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a +"copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + +Open Source Software Licensed Under the BSD 2-Clause License: +The below software in this distribution may have been modified by THL A29 Limited (“Tencent +Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. +---------------------------------------------------------------------------------------- +1. bsdiff and bspatch 4.3 +Copyright 2003-2005 Colin Percival +All rights reserved + + + +Terms of the BSD 2-Clause License: +--------------------------------------------------- +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with + the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Open Source Software Licensed Under the BSD 3-Clause License: +The below software in this distribution may have been modified by THL A29 Limited (“Tencent +Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. +---------------------------------------------------------------------------------------- +1. jbsdiff and jbspatch 0.1.1 +Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) +Based on a direct translation of bsdiff utility by Colin Percival and available at +http://www.daemonology.net/bsdiff/ under the same license. + + + +Terms of the BSD 3-Clause License: +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + * Neither the name of [copyright holder] nor the names of its contributors may be used to endorse + or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NO +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..51e5e319 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +## Tinker +[![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/tinker/blob/master/LICENSE) + +Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk. + +![tinker.png](assets/tinker.png) + +## Getting started +Add tinker-gradle-plugin as a dependency in your main `build.gradle` in the root of your project: + +```gradle +buildscript { + dependencies { + classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.6.0') + } +} +``` + +Then you need to "apply" the plugin and add dependencies by adding the following lines to your `app/build.gradle`. + +```gradle +dependencies { + //optional, help to gen the final application + compile('com.tencent.tinker:tinker-android-anno:1.6.0') + //tinker's main Android lib + compile('com.tencent.tinker:tinker-android-lib:1.6.0') +} +... +... +apply plugin: 'com.tencent.tinker.patch' +``` + +If your app has a class that subclasses android.app.Application, then you need to modify that class, and move all its implements to [SampleApplicationLike](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java) rather than Application: + +```java +-public class YourApplication extends Application { ++public class SampleApplicationLike extends DefaultApplicationLike +``` + +Now you should change your `Application` class, which will be a subclass of [TinkerApplication](https://github.com/Tencent/tinker/blob/master/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java). As you can see from its API, it is an abstract class that does not have a default constructor, so you must define a no-arg constructor as follows: + +```java +public class SampleApplication extends TinkerApplication { + public SampleApplication() { + super( + //tinkerFlags, which types is supported + //dex only, library only, all support + ShareConstants.TINKER_ENABLE_ALL, + // This is passed as a string so the shell application does not + // have a binary dependency on your ApplicationLifeCycle class. + "tinker.sample.android.SampleApplicationLike"); + } +} +``` + +Use `tinker-android-anno` to generate your `Application` is more recommended, you can just add an annotation for your [SampleApplicationLike](http://git.code.oa.com/tinker/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java) class + +```java +@DefaultLifeCycle( +application = "tinker.sample.android.app.SampleApplication", //application name to generate +flags = ShareConstants.TINKER_ENABLE_ALL) //tinkerFlags above +public class SampleApplicationLike extends DefaultApplicationLike +``` + +How to install tinker? learn more at the sample [SampleApplicationLike](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java). + +For proguard, we have already change the proguard config automatic, and also generate the multiDex keep proguard file for you. + +For more tinker configurations, learn more at the sample [app/build.gradle](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/build.gradle). + +## Support +Any problem? + +1. Learn more from [tinker-sample-android](https://github.com/Tencent/tinker/tree/master/tinker-sample-android). +2. Read the [source code](https://github.com/Tencent/tinker/tree/master). +3. Read the [wiki](https://github.com/Tencent/tinker/wiki) or [FAQ](https://github.com/Tencent/tinker/wiki/Tinker-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) for help. +4. Contact us for help. + +## Contributing +For more information about contributing issues or pull requests, see our [Tinker Contributing Guide](https://github.com/Tencent/tinker/blob/master/CONTRIBUTING.md). + +## License +Tinker is under the BSD license. See the [LICENSE](https://github.com/Tencent/tinker/blob/master/LICENSE) file for details. \ No newline at end of file diff --git a/assets/tinker.png b/assets/tinker.png new file mode 100644 index 00000000..6ac1e02b Binary files /dev/null and b/assets/tinker.png differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..c0041cfa --- /dev/null +++ b/build.gradle @@ -0,0 +1,67 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + } + + tasks.withType(Javadoc).all { + enabled = false + options.setEncoding('UTF-8') + } + + apply plugin: 'checkstyle' + + checkstyle { + configFile rootProject.file('checkstyle.xml') + toolVersion '6.19' + ignoreFailures false + showViolations true + } + + task('checkstyle', type: Checkstyle) { + source 'src/main/java' + include '**/*.java' + classpath = files() + } +} + +ext { + minSdkVersion = 10 + compileSdkVersion = 23 + targetSdkVersion = compileSdkVersion + buildToolsVersion = '23.0.2' + javaVersion = JavaVersion.VERSION_1_7 + + GROUP = 'com.tencent.tinker' + VERSION_NAME = "${VERSION_NAME_PREFIX}${VERSION_NAME_SUFFIX}" + + POM_PACKAGING = "pom" + POM_DESCRIPTION= "tinker" + + POM_URL = "https://github.com/Tencent/tinker" + POM_SCM_URL = "https://github.com/Tencent/tinker.git" + POM_ISSUE_URL = 'https://github.com/Tencent/tinker/issues' + + POM_LICENCE_NAME = "BSD License" + POM_LICENCE_URL = "https://opensource.org/licenses/BSD-3-Clause" + POM_LICENCE_DIST = "repo" + + POM_DEVELOPER_ID="Tencent Wechat" + POM_DEVELOPER_NAME="Tencent Wechat, Inc." + + BINTRAY_LICENCE= ['BSD 3-Clause'] + BINTRAY_ORGANIZATION = "tinker" + +} diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..a1436343 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..ef10ccf2 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m + org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +VERSION_NAME_PREFIX=1.6.0 +VERSION_NAME_SUFFIX=-SNAPSHOT \ No newline at end of file diff --git a/gradle/android-artifacts.gradle b/gradle/android-artifacts.gradle new file mode 100644 index 00000000..f240c22f --- /dev/null +++ b/gradle/android-artifacts.gradle @@ -0,0 +1,49 @@ +apply plugin: 'maven-publish' + +task androidJavadocs(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" + classpath += files(ext.androidJar) +} + +task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + classifier = 'javadoc' + from androidJavadocs.destinationDir +} + +task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles +} + +artifacts { + archives androidSourcesJar + archives androidJavadocsJar +} + + +//for local maven test +afterEvaluate { project -> + tasks.all { Task task -> + if (task.name.equalsIgnoreCase('publishTinkerPatchPublicationToMavenLocal')) { + task.dependsOn tasks.getByName('assemble') + } + } +} + +publishing { + publications { + TinkerPatch(MavenPublication) { + groupId = group + artifactId = project.getName() + version = version + // Tell maven to prepare the generated "*.aar" file for publishing + artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") + artifact androidJavadocsJar + } + } +} + +task buildAndPublishLocalMaven(dependsOn: ['build', 'publishTinkerPatchPublicationToMavenLocal']) {} +//depend checkstyle +project.tasks.getByName("check").dependsOn tasks.getByName("checkstyle") diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle new file mode 100644 index 00000000..9e795143 --- /dev/null +++ b/gradle/gradle-mvn-push.gradle @@ -0,0 +1,138 @@ +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: 'com.jfrog.bintray' + + +def isReleaseBuild() { + return version.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : readPropertyFromLocalProperties('RELEASE_REPOSITORY_URL') +} + +def getSnapshotRepositoryUrl() { + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : readPropertyFromLocalProperties('SNAPSHOT_REPOSITORY_URL') +} + +def readPropertyFromLocalProperties(String key) { + Properties properties = new Properties() + try { + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + } catch (Exception e) { + println("load local properties failed msg:${e.message}") + } + return properties.getProperty(key) +} + +def getRepositoryUsername() { + return hasProperty('REPOSITORY_USERNAME') ? REPOSITORY_USERNAME : readPropertyFromLocalProperties('REPOSITORY_USERNAME') +} + +def getRepositoryPassword() { + return hasProperty('REPOSITORY_PASSWORD') ? REPOSITORY_PASSWORD : readPropertyFromLocalProperties('REPOSITORY_PASSWORD') +} + +def getBintrayUser() { + return hasProperty('BINTRAY_USER') ? BINTRAY_USER : readPropertyFromLocalProperties('BINTRAY_USER') +} + +def getBintrayKey() { + return hasProperty('BINTRAY_APIKEY') ? BINTRAY_APIKEY : readPropertyFromLocalProperties('BINTRAY_APIKEY') +} + +uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = version + + repository(url: getReleaseRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + snapshotRepository(url: getSnapshotRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + Distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + + } + } +} + +bintray { + user = getBintrayUser() + key = getBintrayKey() + configurations = ['archives'] + + pkg { + repo = 'maven' + userOrg = BINTRAY_ORGANIZATION + name = "${GROUP}:${POM_ARTIFACT_ID}" + licenses = BINTRAY_LICENCE + vcsUrl = POM_SCM_URL + websiteUrl = POM_URL + issueTrackerUrl = POM_ISSUE_URL + publicDownloadNumbers = true + publish = true + dryRun = false + } +} + +signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives +} + +if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } +} + +task buildAndPublishRepo(dependsOn: ['build', 'uploadArchives']) { + doLast { + println "*published to repo: ${project.group}:${project.name}:${project.version}" + } +} + +//depend checkstyle +tasks.getByName("uploadArchives").dependsOn tasks.getByName("checkstyle") +tasks.getByName("bintrayUpload").dependsOn tasks.getByName("checkstyle") + +tasks.getByName("bintrayUpload") { + it.doFirst { + if (!isReleaseBuild()) { + throw new GradleException("bintrayUpload only support release version") + } + } +} \ No newline at end of file diff --git a/gradle/java-artifacts.gradle b/gradle/java-artifacts.gradle new file mode 100644 index 00000000..0bbd9769 --- /dev/null +++ b/gradle/java-artifacts.gradle @@ -0,0 +1,42 @@ +apply plugin: 'maven-publish' + +task sourcesJar(type: Jar) { + from sourceSets.main.java.srcDirs + classifier = 'sources' +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + + +//for local maven test +afterEvaluate { project -> + tasks.all { Task task -> + if (task.name.equalsIgnoreCase('publishTinkerPatchPublicationToMavenLocal')) { + task.dependsOn tasks.getByName('assemble') + } + } +} + +publishing { + publications { + TinkerPatch(MavenPublication) { + from components.java + groupId = group + artifactId = project.getName() + version = version + } + } +} + +task buildAndPublishLocalMaven(dependsOn: ['build', 'publishTinkerPatchPublicationToMavenLocal']) {} + +//depend checkstyle +project.tasks.getByName("check").dependsOn tasks.getByName("checkstyle") \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..05ef575b Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..05c505ff --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http://android.oa.com/gradle/gradle-2.11-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..1ed082f9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +include ':tinker-commons' +include ':tinker-android:tinker-android-loader' +include ':tinker-android:tinker-android-lib' +include ':tinker-android:tinker-android-anno' +include ':tinker-build:tinker-patch-cli' +include ':tinker-build:tinker-patch-lib' +include ':tinker-build:tinker-patch-gradle-plugin' +include ':third-party:seven-zip' +include ':third-party:aosp-dexutils' +include ':third-party:bsdiff-util' diff --git a/third-party/aosp-dexutils/.gitignore b/third-party/aosp-dexutils/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/third-party/aosp-dexutils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/third-party/aosp-dexutils/NOTICE b/third-party/aosp-dexutils/NOTICE new file mode 100644 index 00000000..a513af6c --- /dev/null +++ b/third-party/aosp-dexutils/NOTICE @@ -0,0 +1,190 @@ + Original work Copyright (c) 2005-2008, The Android Open Source Project + Modified work Copyright (C) 2016 THL A29 Limited, a Tencent company. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/third-party/aosp-dexutils/build.gradle b/third-party/aosp-dexutils/build.gradle new file mode 100644 index 00000000..4153cea7 --- /dev/null +++ b/third-party/aosp-dexutils/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'java' + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +task buildSdk(type: Copy, dependsOn: [build]) { + from('build/libs') { + include '*.jar' + exclude '*javadoc.jar' + exclude '*-sources.jar' + } + into(rootProject.file("buildSdk/android")) +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/third-party/aosp-dexutils/gradle.properties b/third-party/aosp-dexutils/gradle.properties new file mode 100644 index 00000000..e4a05a37 --- /dev/null +++ b/third-party/aosp-dexutils/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=aosp-dexutils +POM_NAME=Dex Utils Lib From AOSP +POM_PACKAGING=jar \ No newline at end of file diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Annotation.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Annotation.java new file mode 100644 index 00000000..59d73fe5 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Annotation.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; + +import static com.tencent.tinker.android.dex.EncodedValueReader.ENCODED_ANNOTATION; + +/** + * An annotation. + */ +public final class Annotation extends Item { + public byte visibility; + public EncodedValue encodedAnnotation; + + public Annotation(int off, byte visibility, EncodedValue encodedAnnotation) { + super(off); + this.visibility = visibility; + this.encodedAnnotation = encodedAnnotation; + } + + public EncodedValueReader getReader() { + return new EncodedValueReader(encodedAnnotation, ENCODED_ANNOTATION); + } + + public int getTypeIndex() { + EncodedValueReader reader = getReader(); + reader.readAnnotation(); + return reader.getAnnotationType(); + } + + @Override public int compareTo(Annotation other) { + return encodedAnnotation.compareTo(other.encodedAnnotation); + } + + @Override + public int byteCountInDex() { + return SizeOf.UBYTE + encodedAnnotation.byteCountInDex(); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSet.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSet.java new file mode 100644 index 00000000..16faedc7 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSet.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section; +import com.tencent.tinker.android.dex.util.CompareUtils; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Structure of AnnotationSet element in Dex file. + */ +public class AnnotationSet extends Section.Item { + public int[] annotationOffsets; + + public AnnotationSet(int off, int[] annotationOffsets) { + super(off); + this.annotationOffsets = annotationOffsets; + } + + @Override + public int compareTo(AnnotationSet other) { + int size = annotationOffsets.length; + int oSize = other.annotationOffsets.length; + + if (size != oSize) { + return CompareUtils.uCompare(size, oSize); + } + + for (int i = 0; i < size; ++i) { + if (annotationOffsets[i] != other.annotationOffsets[i]) { + return CompareUtils.uCompare(annotationOffsets[i], other.annotationOffsets[i]); + } + } + + return 0; + } + + @Override + public int byteCountInDex() { + return SizeOf.UINT * (1 + annotationOffsets.length); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSetRefList.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSetRefList.java new file mode 100644 index 00000000..6389c510 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSetRefList.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section; +import com.tencent.tinker.android.dex.util.CompareUtils; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Structure of AnnotationSetRefList element in Dex file. + */ +public class AnnotationSetRefList extends Section.Item { + public int[] annotationSetRefItems; + + public AnnotationSetRefList(int off, int[] annotationSetRefItems) { + super(off); + this.annotationSetRefItems = annotationSetRefItems; + } + + @Override + public int compareTo(AnnotationSetRefList other) { + int size = annotationSetRefItems.length; + int oSize = other.annotationSetRefItems.length; + + if (size != oSize) { + return CompareUtils.uCompare(size, oSize); + } + + for (int i = 0; i < size; ++i) { + if (annotationSetRefItems[i] != other.annotationSetRefItems[i]) { + return CompareUtils.uCompare(annotationSetRefItems[i], other.annotationSetRefItems[i]); + } + } + + return 0; + } + + @Override + public int byteCountInDex() { + return SizeOf.UINT * (1 + annotationSetRefItems.length); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationsDirectory.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationsDirectory.java new file mode 100644 index 00000000..e5bd1256 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationsDirectory.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section; +import com.tencent.tinker.android.dex.util.CompareUtils; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Structure of AnnotationsDirectory element in Dex file. + */ +public class AnnotationsDirectory extends Section.Item { + public int classAnnotationsOffset; + + /** + * fieldAnnotations[][2]; + * fieldAnnotations[i][0]: fieldIndex, fieldAnnotations[i][1]: annotation set Offset + */ + public int[][] fieldAnnotations; + + /** + * methodAnnotations[][2]; + * methodAnnotations[i][0]: methodIndex, methodAnnotations[i][1]: annotation set Offset + */ + public int[][] methodAnnotations; + + /** + * parameterAnnotations[][2]; + * parameterAnnotations[i][0]: methodIndex, parameterAnnotations[i][1]: annotation set reflist Offset + */ + public int[][] parameterAnnotations; + + public AnnotationsDirectory( + int off, + int classAnnotationsOffset, + int[][] fieldAnnotations, int[][] methodAnnotations, int[][] parameterAnnotations + ) { + super(off); + this.classAnnotationsOffset = classAnnotationsOffset; + this.fieldAnnotations = fieldAnnotations; + this.methodAnnotations = methodAnnotations; + this.parameterAnnotations = parameterAnnotations; + } + + @Override + public int compareTo(AnnotationsDirectory other) { + if (classAnnotationsOffset != other.classAnnotationsOffset) { + return CompareUtils.uCompare(classAnnotationsOffset, other.classAnnotationsOffset); + } + + int fieldsSize = fieldAnnotations.length; + int methodsSize = methodAnnotations.length; + int parameterListSize = parameterAnnotations.length; + int oFieldsSize = other.fieldAnnotations.length; + int oMethodsSize = other.methodAnnotations.length; + int oParameterListSize = other.parameterAnnotations.length; + + if (fieldsSize != oFieldsSize) { + return CompareUtils.sCompare(fieldsSize, oFieldsSize); + } + + if (methodsSize != oMethodsSize) { + return CompareUtils.sCompare(methodsSize, oMethodsSize); + } + + if (parameterListSize != oParameterListSize) { + return CompareUtils.sCompare(parameterListSize, oParameterListSize); + } + + for (int i = 0; i < fieldsSize; ++i) { + int fieldIdx = fieldAnnotations[i][0]; + int annotationOffset = fieldAnnotations[i][1]; + int othFieldIdx = other.fieldAnnotations[i][0]; + int othAnnotationOffset = other.fieldAnnotations[i][1]; + + if (fieldIdx != othFieldIdx) { + return CompareUtils.uCompare(fieldIdx, othFieldIdx); + } + + if (annotationOffset != othAnnotationOffset) { + return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); + } + } + + for (int i = 0; i < methodsSize; ++i) { + int methodIdx = methodAnnotations[i][0]; + int annotationOffset = methodAnnotations[i][1]; + int othMethodIdx = other.methodAnnotations[i][0]; + int othAnnotationOffset = other.methodAnnotations[i][1]; + + if (methodIdx != othMethodIdx) { + return CompareUtils.uCompare(methodIdx, othMethodIdx); + } + + if (annotationOffset != othAnnotationOffset) { + return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); + } + } + + for (int i = 0; i < parameterListSize; ++i) { + int methodIdx = parameterAnnotations[i][0]; + int annotationOffset = parameterAnnotations[i][1]; + int othMethodIdx = other.parameterAnnotations[i][0]; + int othAnnotationOffset = other.parameterAnnotations[i][1]; + + if (methodIdx != othMethodIdx) { + return CompareUtils.uCompare(methodIdx, othMethodIdx); + } + + if (annotationOffset != othAnnotationOffset) { + return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); + } + } + + return 0; + } + + @Override + public int byteCountInDex() { + return SizeOf.UINT * (4 + 2 * (fieldAnnotations.length + methodAnnotations.length + parameterAnnotations.length)); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassData.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassData.java new file mode 100644 index 00000000..4824957e --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassData.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class ClassData extends Item { + public Field[] staticFields; + public Field[] instanceFields; + public Method[] directMethods; + public Method[] virtualMethods; + + public ClassData(int off, Field[] staticFields, Field[] instanceFields, + Method[] directMethods, Method[] virtualMethods) { + super(off); + + this.staticFields = staticFields; + this.instanceFields = instanceFields; + this.directMethods = directMethods; + this.virtualMethods = virtualMethods; + } + + @Override + public int compareTo(ClassData other) { + int res = CompareUtils.aArrCompare(staticFields, other.staticFields); + if (res != 0) { + return res; + } + res = CompareUtils.aArrCompare(instanceFields, other.instanceFields); + if (res != 0) { + return res; + } + res = CompareUtils.aArrCompare(directMethods, other.directMethods); + if (res != 0) { + return res; + } + return CompareUtils.aArrCompare(virtualMethods, other.virtualMethods); + } + + @Override + public int byteCountInDex() { + int res = Leb128.unsignedLeb128Size(staticFields.length); + res += Leb128.unsignedLeb128Size(instanceFields.length); + res += Leb128.unsignedLeb128Size(directMethods.length); + res += Leb128.unsignedLeb128Size(virtualMethods.length); + res += calcFieldsSize(staticFields); + res += calcFieldsSize(instanceFields); + res += calcMethodsSize(directMethods); + res += calcMethodsSize(virtualMethods); + return res; + } + + private int calcFieldsSize(Field[] fields) { + int res = 0; + int prevFieldIndex = 0; + for (Field field : fields) { + int fieldIndexDelta = field.fieldIndex - prevFieldIndex; + prevFieldIndex = field.fieldIndex; + res += Leb128.unsignedLeb128Size(fieldIndexDelta) + Leb128.unsignedLeb128Size(field.accessFlags); + } + return res; + } + + private int calcMethodsSize(Method[] methods) { + int res = 0; + int prevMethodIndex = 0; + for (Method method : methods) { + int methodIndexDelta = method.methodIndex - prevMethodIndex; + prevMethodIndex = method.methodIndex; + res += Leb128.unsignedLeb128Size(methodIndexDelta) + + Leb128.unsignedLeb128Size(method.accessFlags) + + Leb128.unsignedLeb128Size(method.codeOffset); + } + return res; + } + + public static class Field implements Comparable { + public int fieldIndex; + public int accessFlags; + + public Field(int fieldIndex, int accessFlags) { + this.fieldIndex = fieldIndex; + this.accessFlags = accessFlags; + } + + @Override + public int compareTo(Field other) { + int res = CompareUtils.uCompare(fieldIndex, other.fieldIndex); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(accessFlags, other.accessFlags); + } + } + + public static class Method implements Comparable { + public int methodIndex; + public int accessFlags; + public int codeOffset; + + public Method(int methodIndex, int accessFlags, int codeOffset) { + this.methodIndex = methodIndex; + this.accessFlags = accessFlags; + this.codeOffset = codeOffset; + } + + @Override + public int compareTo(Method other) { + int res = CompareUtils.uCompare(methodIndex, other.methodIndex); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(accessFlags, other.accessFlags); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(codeOffset, other.codeOffset); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassDef.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassDef.java new file mode 100644 index 00000000..c507324a --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassDef.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.CompareUtils; + +/** + * A type definition. + */ +public final class ClassDef extends TableOfContents.Section.Item { + public static final int NO_INDEX = -1; + public static final int NO_OFFSET = 0; + + public int typeIndex; + public int accessFlags; + public int supertypeIndex; + public int interfacesOffset; + public int sourceFileIndex; + public int annotationsOffset; + public int classDataOffset; + public int staticValuesOffset; + + public ClassDef(int off, int typeIndex, int accessFlags, + int supertypeIndex, int interfacesOffset, int sourceFileIndex, + int annotationsOffset, int classDataOffset, int staticValuesOffset) { + super(off); + this.typeIndex = typeIndex; + this.accessFlags = accessFlags; + this.supertypeIndex = supertypeIndex; + this.interfacesOffset = interfacesOffset; + this.sourceFileIndex = sourceFileIndex; + this.annotationsOffset = annotationsOffset; + this.classDataOffset = classDataOffset; + this.staticValuesOffset = staticValuesOffset; + } + + @Override + public int compareTo(ClassDef other) { + int res = CompareUtils.uCompare(typeIndex, other.typeIndex); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(accessFlags, other.accessFlags); + if (res != 0) { + return res; + } + res = CompareUtils.uCompare(supertypeIndex, other.supertypeIndex); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(interfacesOffset, other.interfacesOffset); + if (res != 0) { + return res; + } + res = CompareUtils.uCompare(sourceFileIndex, other.sourceFileIndex); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(annotationsOffset, other.annotationsOffset); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(classDataOffset, other.classDataOffset); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(staticValuesOffset, other.staticValuesOffset); + } + + @Override + public int byteCountInDex() { + return SizeOf.CLASS_DEF_ITEM; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Code.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Code.java new file mode 100644 index 00000000..4bb34b8a --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Code.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class Code extends Item { + public int registersSize; + public int insSize; + public int outsSize; + public int debugInfoOffset; + public short[] instructions; + public Try[] tries; + public CatchHandler[] catchHandlers; + + public Code(int off, int registersSize, int insSize, int outsSize, int debugInfoOffset, + short[] instructions, Try[] tries, CatchHandler[] catchHandlers) { + super(off); + this.registersSize = registersSize; + this.insSize = insSize; + this.outsSize = outsSize; + this.debugInfoOffset = debugInfoOffset; + this.instructions = instructions; + this.tries = tries; + this.catchHandlers = catchHandlers; + } + + @Override + public int compareTo(Code other) { + int res = CompareUtils.sCompare(registersSize, other.registersSize); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(insSize, other.insSize); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(outsSize, other.outsSize); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(debugInfoOffset, other.debugInfoOffset); + if (res != 0) { + return res; + } + res = CompareUtils.uArrCompare(instructions, other.instructions); + if (res != 0) { + return res; + } + res = CompareUtils.aArrCompare(tries, other.tries); + if (res != 0) { + return res; + } + return CompareUtils.aArrCompare(catchHandlers, other.catchHandlers); + } + + @Override + public int byteCountInDex() { + int insnsSize = instructions.length; + int res = 4 * SizeOf.USHORT + 2 * SizeOf.UINT + insnsSize * SizeOf.USHORT; + if (tries.length > 0) { + if ((insnsSize & 1) == 1) { + res += SizeOf.USHORT; + } + res += tries.length * SizeOf.TRY_ITEM; + + int catchHandlerSize = catchHandlers.length; + res += Leb128.unsignedLeb128Size(catchHandlerSize); + + for (CatchHandler catchHandler : catchHandlers) { + int typeIdxAddrPairCount = catchHandler.typeIndexes.length; + if (catchHandler.catchAllAddress != -1) { + res += Leb128.signedLeb128Size(-typeIdxAddrPairCount) + + Leb128.unsignedLeb128Size(catchHandler.catchAllAddress); + } else { + res += Leb128.signedLeb128Size(typeIdxAddrPairCount); + } + for (int i = 0; i < typeIdxAddrPairCount; ++i) { + res += Leb128.unsignedLeb128Size(catchHandler.typeIndexes[i]) + + Leb128.unsignedLeb128Size(catchHandler.addresses[i]); + } + } + } + + return res; + } + + public static class Try implements Comparable { + public int startAddress; + public int instructionCount; + public int catchHandlerIndex; + + public Try(int startAddress, int instructionCount, int catchHandlerIndex) { + this.startAddress = startAddress; + this.instructionCount = instructionCount; + this.catchHandlerIndex = catchHandlerIndex; + } + + @Override + public int compareTo(Try other) { + int res = CompareUtils.sCompare(startAddress, other.startAddress); + if (res != 0) { + return res; + } + res = CompareUtils.sCompare(instructionCount, other.instructionCount); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(catchHandlerIndex, other.catchHandlerIndex); + } + } + + public static class CatchHandler implements Comparable { + public int[] typeIndexes; + public int[] addresses; + public int catchAllAddress; + public int offset; + + public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress, int offset) { + this.typeIndexes = typeIndexes; + this.addresses = addresses; + this.catchAllAddress = catchAllAddress; + this.offset = offset; + } + + @Override + public int compareTo(CatchHandler other) { + int res = CompareUtils.sArrCompare(typeIndexes, other.typeIndexes); + if (res != 0) { + return res; + } + res = CompareUtils.sArrCompare(addresses, other.addresses); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(catchAllAddress, other.catchAllAddress); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DebugInfoItem.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DebugInfoItem.java new file mode 100644 index 00000000..aff6a336 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DebugInfoItem.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Structure of DebugInfoItem element in Dex file. + */ +public class DebugInfoItem extends Item { + public static final byte DBG_END_SEQUENCE = 0x00; + public static final byte DBG_ADVANCE_PC = 0x01; + public static final byte DBG_ADVANCE_LINE = 0x02; + public static final byte DBG_START_LOCAL = 0x03; + public static final byte DBG_START_LOCAL_EXTENDED = 0x04; + public static final byte DBG_END_LOCAL = 0x05; + public static final byte DBG_RESTART_LOCAL = 0x06; + public static final byte DBG_SET_PROLOGUE_END = 0x07; + public static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; + public static final byte DBG_SET_FILE = 0x09; + + public int lineStart; + public int[] parameterNames; + + public byte[] infoSTM; + + public DebugInfoItem(int off, int lineStart, int[] parameterNames, byte[] infoSTM) { + super(off); + this.lineStart = lineStart; + this.parameterNames = parameterNames; + this.infoSTM = infoSTM; + } + + @Override + public int compareTo(DebugInfoItem o) { + int origLineStart = lineStart; + int destLineStart = o.lineStart; + if (origLineStart != destLineStart) { + return origLineStart - destLineStart; + } + + int cmpRes = CompareUtils.uArrCompare(parameterNames, o.parameterNames); + if (cmpRes != 0) return cmpRes; + + cmpRes = CompareUtils.uArrCompare(infoSTM, o.infoSTM); + return cmpRes; + } + + @Override + public int byteCountInDex() { + int byteCount = Leb128.unsignedLeb128Size(lineStart) + Leb128.unsignedLeb128Size(parameterNames.length); + for (int pn : parameterNames) { + byteCount += Leb128.unsignedLeb128p1Size(pn); + } + byteCount += infoSTM.length * SizeOf.UBYTE; + return byteCount; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Dex.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Dex.java new file mode 100644 index 00000000..f22da85a --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Dex.java @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dex.util.FileUtils; +import com.tencent.tinker.android.dx.util.Hex; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.AbstractList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.zip.Adler32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * The bytes of a dex file in memory for reading and writing. All int offsets + * are unsigned. + */ +public final class Dex { + // Provided as a convenience to avoid a memory allocation to benefit Dalvik. + // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik. + static final short[] EMPTY_SHORT_ARRAY = new short[0]; + private static final int CHECKSUM_OFFSET = 8; + private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + SizeOf.CHECKSUM; + private final TableOfContents tableOfContents = new TableOfContents(); + private final StringTable strings = new StringTable(); + private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable(); + private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable(); + private final ProtoIdTable protoIds = new ProtoIdTable(); + private final FieldIdTable fieldIds = new FieldIdTable(); + private final MethodIdTable methodIds = new MethodIdTable(); + private final ClassDefTable classDefs = new ClassDefTable(); + private ByteBuffer data; + private int nextSectionStart = 0; + private byte[] signature = null; + + /** + * Creates a new dex that reads from {@code data}. It is an error to modify + * {@code data} after using it to create a dex buffer. + */ + public Dex(byte[] data) throws IOException { + this(ByteBuffer.wrap(data)); + } + + private Dex(ByteBuffer data) throws IOException { + this.data = data; + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.tableOfContents.readFrom(this); + } + + /** + * Creates a new empty dex of the specified size. + */ + public Dex(int byteCount) { + this.data = ByteBuffer.wrap(new byte[byteCount]); + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.tableOfContents.fileSize = byteCount; + } + + /** + * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. + */ + public Dex(InputStream in) throws IOException { + loadFrom(in); + } + + public Dex(InputStream in, int initSize) throws IOException { + loadFrom(in, initSize); + } + + /** + * Creates a new dex buffer from the dex file {@code file}. + */ + public Dex(File file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("file is null."); + } + + if (FileUtils.hasArchiveSuffix(file.getName())) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); + if (entry != null) { + InputStream inputStream = null; + try { + inputStream = zipFile.getInputStream(entry); + loadFrom(inputStream, (int) entry.getSize()); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } else { + throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); + } + } finally { + if (zipFile != null) { + try { + zipFile.close(); + } catch (Exception e) { + // ignored. + } + } + } + } else if (file.getName().endsWith(".dex")) { + InputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(file)); + loadFrom(in, (int) file.length()); + } catch (Exception e) { + throw new DexException(e); + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception e) { + // ignored. + } + } + } + } else { + throw new DexException("unknown output extension: " + file); + } + } + + private static void checkBounds(int index, int length) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); + } + } + + private void loadFrom(InputStream in) throws IOException { + loadFrom(in, 0); + } + + private void loadFrom(InputStream in, int initSize) throws IOException { + byte[] rawData = FileUtils.readStream(in, initSize); + this.data = ByteBuffer.wrap(rawData); + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.tableOfContents.readFrom(this); + } + + public void writeTo(OutputStream out) throws IOException { + byte[] rawData = data.array(); + out.write(rawData); + out.flush(); + } + + public void writeTo(File dexOut) throws IOException { + OutputStream out = null; + try { + out = new BufferedOutputStream(new FileOutputStream(dexOut)); + writeTo(out); + } catch (Exception e) { + throw new DexException(e); + } finally { + if (out != null) { + try { + out.close(); + } catch (Exception e) { + // ignored. + } + } + } + } + + public TableOfContents getTableOfContents() { + return tableOfContents; + } + + /** + * IMPORTANT To open a dex section by {@code TableOfContents.Section}, + * please use {@code openSection(TableOfContents.Section tocSec)} instead of + * passing tocSec.off to this method. + * + * Because dex section returned by this method never checks + * tocSec's bound when reading or writing data. + */ + public Section openSection(int position) { + if (position < 0 || position >= data.capacity()) { + throw new IllegalArgumentException( + "position=" + position + " length=" + data.capacity() + ); + } + ByteBuffer sectionData = data.duplicate(); + sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? + sectionData.position(position); + sectionData.limit(data.capacity()); + return new Section("temp-section", sectionData); + } + + public Section openSection(TableOfContents.Section tocSec) { + int position = tocSec.off; + if (position < 0 || position >= data.capacity()) { + throw new IllegalArgumentException( + "position=" + position + " length=" + data.capacity() + ); + } + ByteBuffer sectionData = data.duplicate(); + sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? + sectionData.position(position); + sectionData.limit(position + tocSec.byteCount); + return new Section("section", sectionData); + } + + public Section appendSection(int maxByteCount, String name) { + int limit = nextSectionStart + maxByteCount; + ByteBuffer sectionData = data.duplicate(); + sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? + sectionData.position(nextSectionStart); + sectionData.limit(limit); + Section result = new Section(name, sectionData); + nextSectionStart = limit; + return result; + } + + public int getLength() { + return data.capacity(); + } + + public int getNextSectionStart() { + return nextSectionStart; + } + + /** + * Returns a copy of the the bytes of this dex. + */ + public byte[] getBytes() { + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + byte[] result = new byte[data.capacity()]; + data.position(0); + data.get(result); + return result; + } + + public List strings() { + return strings; + } + + public List typeIds() { + return typeIds; + } + + public List typeNames() { + return typeNames; + } + + public List protoIds() { + return protoIds; + } + + public List fieldIds() { + return fieldIds; + } + + public List methodIds() { + return methodIds; + } + + public List classDefs() { + return classDefs; + } + + public Iterable classDefIterable() { + return new ClassDefIterable(); + } + + public ClassData readClassData(ClassDef classDef) { + int offset = classDef.classDataOffset; + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return openSection(offset).readClassData(); + } + + public Code readCode(ClassData.Method method) { + int offset = method.codeOffset; + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return openSection(offset).readCode(); + } + + /** + * Returns the signature of all but the first 32 bytes of this dex. The + * first 32 bytes of dex files are not specified to be included in the + * signature. + */ + public byte[] computeSignature(boolean forceRecompute) { + if (this.signature != null) { + if (!forceRecompute) { + return this.signature; + } + } + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(); + } + byte[] buffer = new byte[8192]; + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + data.limit(data.capacity()); + data.position(SIGNATURE_OFFSET + SizeOf.SIGNATURE); + while (data.hasRemaining()) { + int count = Math.min(buffer.length, data.remaining()); + data.get(buffer, 0, count); + digest.update(buffer, 0, count); + } + return (this.signature = digest.digest()); + } + + private String bytesToHexString(byte[] bytes) { + StringBuilder strBuilder = new StringBuilder(bytes.length << 1); + for (byte b : bytes) { + strBuilder.append(Hex.u1(b)); + } + return strBuilder.toString(); + } + + /** + * Returns the checksum of all but the first 12 bytes of {@code dex}. + */ + public int computeChecksum() throws IOException { + Adler32 adler32 = new Adler32(); + byte[] buffer = new byte[8192]; + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + data.limit(data.capacity()); + data.position(CHECKSUM_OFFSET + SizeOf.CHECKSUM); + while (data.hasRemaining()) { + int count = Math.min(buffer.length, data.remaining()); + data.get(buffer, 0, count); + adler32.update(buffer, 0, count); + } + return (int) adler32.getValue(); + } + + /** + * Generates the signature and checksum of the dex file {@code out} and + * writes them to the file. + */ + public void writeHashes() throws IOException { + openSection(SIGNATURE_OFFSET).write(computeSignature(true)); + openSection(CHECKSUM_OFFSET).writeInt(computeChecksum()); + } + + /** + * Look up a field id name index from a field index. Cheaper than: + * {@code fieldIds().get(fieldDexIndex).getNameIndex();} + */ + public int nameIndexFromFieldIndex(int fieldIndex) { + checkBounds(fieldIndex, tableOfContents.fieldIds.size); + int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); + position += SizeOf.USHORT; // declaringClassIndex + position += SizeOf.USHORT; // typeIndex + return data.getInt(position); // nameIndex + } + + public int findStringIndex(String s) { + return Collections.binarySearch(strings, s); + } + + public int findTypeIndex(String descriptor) { + return Collections.binarySearch(typeNames, descriptor); + } + + public int findFieldIndex(FieldId fieldId) { + return Collections.binarySearch(fieldIds, fieldId); + } + + public int findMethodIndex(MethodId methodId) { + return Collections.binarySearch(methodIds, methodId); + } + + public int findClassDefIndexFromTypeIndex(int typeIndex) { + checkBounds(typeIndex, tableOfContents.typeIds.size); + if (!tableOfContents.classDefs.exists()) { + return -1; + } + for (int i = 0; i < tableOfContents.classDefs.size; i++) { + if (typeIndexFromClassDefIndex(i) == typeIndex) { + return i; + } + } + return -1; + } + + /** + * Look up a field id type index from a field index. Cheaper than: + * {@code fieldIds().get(fieldDexIndex).getTypeIndex();} + */ + public int typeIndexFromFieldIndex(int fieldIndex) { + checkBounds(fieldIndex, tableOfContents.fieldIds.size); + int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); + position += SizeOf.USHORT; // declaringClassIndex + return data.getShort(position) & 0xFFFF; // typeIndex + } + + /** + * Look up a method id declaring class index from a method index. Cheaper than: + * {@code methodIds().get(methodIndex).getDeclaringClassIndex();} + */ + public int declaringClassIndexFromMethodIndex(int methodIndex) { + checkBounds(methodIndex, tableOfContents.methodIds.size); + int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); + return data.getShort(position) & 0xFFFF; // declaringClassIndex + } + + /** + * Look up a method id name index from a method index. Cheaper than: + * {@code methodIds().get(methodIndex).getNameIndex();} + */ + public int nameIndexFromMethodIndex(int methodIndex) { + checkBounds(methodIndex, tableOfContents.methodIds.size); + int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); + position += SizeOf.USHORT; // declaringClassIndex + position += SizeOf.USHORT; // protoIndex + return data.getInt(position); // nameIndex + } + + /** + * Look up a parameter type ids from a method index. Cheaper than: + * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} + */ + public short[] parameterTypeIndicesFromMethodIndex(int methodIndex) { + checkBounds(methodIndex, tableOfContents.methodIds.size); + int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); + position += SizeOf.USHORT; // declaringClassIndex + int protoIndex = data.getShort(position) & 0xFFFF; + checkBounds(protoIndex, tableOfContents.protoIds.size); + position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); + position += SizeOf.UINT; // shortyIndex + position += SizeOf.UINT; // returnTypeIndex + int parametersOffset = data.getInt(position); + if (parametersOffset == 0) { + return EMPTY_SHORT_ARRAY; + } + position = parametersOffset; + int size = data.getInt(position); + if (size <= 0) { + throw new AssertionError("Unexpected parameter type list size: " + size); + } + position += SizeOf.UINT; + short[] types = new short[size]; + for (int i = 0; i < size; i++) { + types[i] = data.getShort(position); + position += SizeOf.USHORT; + } + return types; + } + + /** + * Look up a parameter type ids from a methodId. Cheaper than: + * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} + */ + public short[] parameterTypeIndicesFromMethodId(MethodId methodId) { + int protoIndex = methodId.protoIndex & 0xFFFF; + checkBounds(protoIndex, tableOfContents.protoIds.size); + int position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); + position += SizeOf.UINT; // shortyIndex + position += SizeOf.UINT; // returnTypeIndex + int parametersOffset = data.getInt(position); + if (parametersOffset == 0) { + return EMPTY_SHORT_ARRAY; + } + position = parametersOffset; + int size = data.getInt(position); + if (size <= 0) { + throw new AssertionError("Unexpected parameter type list size: " + size); + } + position += SizeOf.UINT; + short[] types = new short[size]; + for (int i = 0; i < size; i++) { + types[i] = data.getShort(position); + position += SizeOf.USHORT; + } + return types; + } + + /** + * Look up a method id return type index from a method index. Cheaper than: + * {@code protoIds().get(methodIds().get(methodDexIndex).getProtoIndex()).getReturnTypeIndex();} + */ + public int returnTypeIndexFromMethodIndex(int methodIndex) { + checkBounds(methodIndex, tableOfContents.methodIds.size); + int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); + position += SizeOf.USHORT; // declaringClassIndex + int protoIndex = data.getShort(position) & 0xFFFF; + checkBounds(protoIndex, tableOfContents.protoIds.size); + position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); + position += SizeOf.UINT; // shortyIndex + return data.getInt(position); // returnTypeIndex + } + + /** + * Look up a descriptor index from a type index. Cheaper than: + * {@code openSection(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();} + */ + public int descriptorIndexFromTypeIndex(int typeIndex) { + checkBounds(typeIndex, tableOfContents.typeIds.size); + int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex); + return data.getInt(position); + } + + /** + * Look up a type index index from a class def index. + */ + public int typeIndexFromClassDefIndex(int classDefIndex) { + checkBounds(classDefIndex, tableOfContents.classDefs.size); + int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); + return data.getInt(position); + } + + /** + * Look up an annotation directory offset from a class def index. + */ + public int annotationDirectoryOffsetFromClassDefIndex(int classDefIndex) { + checkBounds(classDefIndex, tableOfContents.classDefs.size); + int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); + position += SizeOf.UINT; // type + position += SizeOf.UINT; // accessFlags + position += SizeOf.UINT; // superType + position += SizeOf.UINT; // interfacesOffset + position += SizeOf.UINT; // sourceFileIndex + return data.getInt(position); + } + + /** + * Look up interface types indices from a return type index from a method index. Cheaper than: + * {@code ...getClassDef(classDefIndex).getInterfaces();} + */ + public short[] interfaceTypeIndicesFromClassDefIndex(int classDefIndex) { + checkBounds(classDefIndex, tableOfContents.classDefs.size); + int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); + position += SizeOf.UINT; // type + position += SizeOf.UINT; // accessFlags + position += SizeOf.UINT; // superType + int interfacesOffset = data.getInt(position); + if (interfacesOffset == 0) { + return EMPTY_SHORT_ARRAY; + } + position = interfacesOffset; + int size = data.getInt(position); + if (size <= 0) { + throw new AssertionError("Unexpected interfaces list size: " + size); + } + position += SizeOf.UINT; + short[] types = new short[size]; + for (int i = 0; i < size; i++) { + types[i] = data.getShort(position); + position += SizeOf.USHORT; + } + return types; + } + + public short[] interfaceTypeIndicesFromClassDef(ClassDef classDef) { + int position = classDef.off; + position += SizeOf.UINT; // type + position += SizeOf.UINT; // accessFlags + position += SizeOf.UINT; // superType + int interfacesOffset = data.getInt(position); + if (interfacesOffset == 0) { + return EMPTY_SHORT_ARRAY; + } + position = interfacesOffset; + int size = data.getInt(position); + if (size <= 0) { + throw new AssertionError("Unexpected interfaces list size: " + size); + } + position += SizeOf.UINT; + short[] types = new short[size]; + for (int i = 0; i < size; i++) { + types[i] = data.getShort(position); + position += SizeOf.USHORT; + } + return types; + } + + public final class Section extends DexDataBuffer { + private final String name; + + private Section(String name, ByteBuffer data) { + super(data); + this.name = name; + } + + /** + * @inheritDoc + */ + @Override + public StringData readStringData() { + ensureFourBytesAligned(tableOfContents.stringDatas, false); + return super.readStringData(); + } + + /** + * @inheritDoc + */ + @Override + public TypeList readTypeList() { + ensureFourBytesAligned(tableOfContents.typeLists, false); + return super.readTypeList(); + } + + /** + * @inheritDoc + */ + @Override + public FieldId readFieldId() { + ensureFourBytesAligned(tableOfContents.fieldIds, false); + return super.readFieldId(); + } + + /** + * @inheritDoc + */ + @Override + public MethodId readMethodId() { + ensureFourBytesAligned(tableOfContents.methodIds, false); + return super.readMethodId(); + } + + /** + * @inheritDoc + */ + @Override + public ProtoId readProtoId() { + ensureFourBytesAligned(tableOfContents.protoIds, false); + return super.readProtoId(); + } + + /** + * @inheritDoc + */ + @Override + public ClassDef readClassDef() { + ensureFourBytesAligned(tableOfContents.classDefs, false); + return super.readClassDef(); + } + + /** + * @inheritDoc + */ + @Override + public Code readCode() { + ensureFourBytesAligned(tableOfContents.codes, false); + return super.readCode(); + } + + /** + * @inheritDoc + */ + @Override + public DebugInfoItem readDebugInfoItem() { + ensureFourBytesAligned(tableOfContents.debugInfos, false); + return super.readDebugInfoItem(); + } + + /** + * @inheritDoc + */ + @Override + public ClassData readClassData() { + ensureFourBytesAligned(tableOfContents.classDatas, false); + return super.readClassData(); + } + + /** + * @inheritDoc + */ + @Override + public Annotation readAnnotation() { + ensureFourBytesAligned(tableOfContents.annotations, false); + return super.readAnnotation(); + } + + /** + * @inheritDoc + */ + @Override + public AnnotationSet readAnnotationSet() { + ensureFourBytesAligned(tableOfContents.annotationSets, false); + return super.readAnnotationSet(); + } + + /** + * @inheritDoc + */ + @Override + public AnnotationSetRefList readAnnotationSetRefList() { + ensureFourBytesAligned(tableOfContents.annotationSetRefLists, false); + return super.readAnnotationSetRefList(); + } + + /** + * @inheritDoc + */ + @Override + public AnnotationsDirectory readAnnotationsDirectory() { + ensureFourBytesAligned(tableOfContents.annotationsDirectories, false); + return super.readAnnotationsDirectory(); + } + + /** + * @inheritDoc + */ + @Override + public EncodedValue readEncodedArray() { + ensureFourBytesAligned(tableOfContents.encodedArrays, false); + return super.readEncodedArray(); + } + + private void ensureFourBytesAligned(TableOfContents.Section tocSec, boolean isFillWithZero) { + if (tocSec.isElementFourByteAligned) { + if (isFillWithZero) { + alignToFourBytesWithZeroFill(); + } else { + alignToFourBytes(); + } + } + } + + /** + * @inheritDoc + */ + @Override + public int writeStringData(StringData stringData) { + ensureFourBytesAligned(tableOfContents.stringDatas, true); + return super.writeStringData(stringData); + } + + /** + * @inheritDoc + */ + @Override + public int writeTypeList(TypeList typeList) { + ensureFourBytesAligned(tableOfContents.typeLists, true); + return super.writeTypeList(typeList); + } + + /** + * @inheritDoc + */ + @Override + public int writeFieldId(FieldId fieldId) { + ensureFourBytesAligned(tableOfContents.fieldIds, true); + return super.writeFieldId(fieldId); + } + + /** + * @inheritDoc + */ + @Override + public int writeMethodId(MethodId methodId) { + ensureFourBytesAligned(tableOfContents.methodIds, true); + return super.writeMethodId(methodId); + } + + /** + * @inheritDoc + */ + @Override + public int writeProtoId(ProtoId protoId) { + ensureFourBytesAligned(tableOfContents.protoIds, true); + return super.writeProtoId(protoId); + } + + /** + * @inheritDoc + */ + @Override + public int writeClassDef(ClassDef classDef) { + ensureFourBytesAligned(tableOfContents.classDefs, true); + return super.writeClassDef(classDef); + } + + /** + * @inheritDoc + */ + @Override + public int writeCode(Code code) { + ensureFourBytesAligned(tableOfContents.codes, true); + return super.writeCode(code); + } + + /** + * @inheritDoc + */ + @Override + public int writeDebugInfoItem(DebugInfoItem debugInfoItem) { + ensureFourBytesAligned(tableOfContents.debugInfos, true); + return super.writeDebugInfoItem(debugInfoItem); + } + + /** + * @inheritDoc + */ + @Override + public int writeClassData(ClassData classData) { + ensureFourBytesAligned(tableOfContents.classDatas, true); + return super.writeClassData(classData); + } + + /** + * @inheritDoc + */ + @Override + public int writeAnnotation(Annotation annotation) { + ensureFourBytesAligned(tableOfContents.annotations, true); + return super.writeAnnotation(annotation); + } + + /** + * @inheritDoc + */ + @Override + public int writeAnnotationSet(AnnotationSet annotationSet) { + ensureFourBytesAligned(tableOfContents.annotationSets, true); + return super.writeAnnotationSet(annotationSet); + } + + /** + * @inheritDoc + */ + @Override + public int writeAnnotationSetRefList(AnnotationSetRefList annotationSetRefList) { + ensureFourBytesAligned(tableOfContents.annotationSetRefLists, true); + return super.writeAnnotationSetRefList(annotationSetRefList); + } + + /** + * @inheritDoc + */ + @Override + public int writeAnnotationsDirectory(AnnotationsDirectory annotationsDirectory) { + ensureFourBytesAligned(tableOfContents.annotationsDirectories, true); + return super.writeAnnotationsDirectory(annotationsDirectory); + } + + /** + * @inheritDoc + */ + @Override + public int writeEncodedArray(EncodedValue encodedValue) { + ensureFourBytesAligned(tableOfContents.encodedArrays, true); + return super.writeEncodedArray(encodedValue); + } + } + + private final class StringTable extends AbstractList implements RandomAccess { + @Override public String get(int index) { + checkBounds(index, tableOfContents.stringIds.size); + int stringOff = openSection(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)).readInt(); + return openSection(stringOff).readStringData().value; + } + @Override public int size() { + return tableOfContents.stringIds.size; + } + } + + private final class TypeIndexToDescriptorIndexTable extends AbstractList + implements RandomAccess { + @Override public Integer get(int index) { + return descriptorIndexFromTypeIndex(index); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + } + + private final class TypeIndexToDescriptorTable extends AbstractList + implements RandomAccess { + @Override public String get(int index) { + return strings.get(descriptorIndexFromTypeIndex(index)); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + } + + private final class ProtoIdTable extends AbstractList implements RandomAccess { + @Override public ProtoId get(int index) { + checkBounds(index, tableOfContents.protoIds.size); + return openSection(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) + .readProtoId(); + } + @Override public int size() { + return tableOfContents.protoIds.size; + } + } + + private final class FieldIdTable extends AbstractList implements RandomAccess { + @Override public FieldId get(int index) { + checkBounds(index, tableOfContents.fieldIds.size); + return openSection(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readFieldId(); + } + @Override public int size() { + return tableOfContents.fieldIds.size; + } + } + + private final class MethodIdTable extends AbstractList implements RandomAccess { + @Override public MethodId get(int index) { + checkBounds(index, tableOfContents.methodIds.size); + return openSection(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readMethodId(); + } + @Override public int size() { + return tableOfContents.methodIds.size; + } + } + + private final class ClassDefTable extends AbstractList implements RandomAccess { + @Override + public ClassDef get(int index) { + checkBounds(index, tableOfContents.classDefs.size); + return openSection(tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * index)) + .readClassDef(); + } + + @Override + public int size() { + return tableOfContents.classDefs.size; + } + } + + private final class ClassDefIterator implements Iterator { + private final Section in = openSection(tableOfContents.classDefs); + private int count = 0; + + @Override + public boolean hasNext() { + return count < tableOfContents.classDefs.size; + } + @Override + public ClassDef next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return in.readClassDef(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class ClassDefIterable implements Iterable { + public Iterator iterator() { + return !tableOfContents.classDefs.exists() + ? Collections.emptySet().iterator() + : new ClassDefIterator(); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexException.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexException.java new file mode 100644 index 00000000..28d9adfc --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +/** + * Thrown when there's a format problem reading, writing, or generally + * processing a dex file. + */ +public class DexException extends RuntimeException { + static final long serialVersionUID = 1L; + + public DexException(String message) { + super(message); + } + + public DexException(Throwable cause) { + super(cause); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexFormat.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexFormat.java new file mode 100644 index 00000000..564e3784 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexFormat.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +/** + * Constants that show up in and are otherwise related to {@code .dex} + * files, and helper methods for same. + */ +public final class DexFormat { + /** + * API level to target in order to produce the most modern file + * format + */ + public static final int API_CURRENT = 14; + /** API level to target in order to suppress extended opcode usage */ + public static final int API_NO_EXTENDED_OPCODES = 13; + /** + * file name of the primary {@code .dex} file inside an + * application or library {@code .jar} file + */ + public static final String DEX_IN_JAR_NAME = "classes.dex"; + /** common prefix for all dex file "magic numbers" */ + public static final String MAGIC_PREFIX = "dex\n"; + /** common suffix for all dex file "magic numbers" */ + public static final String MAGIC_SUFFIX = "\0"; + /** dex file version number for the current format variant */ + public static final String VERSION_CURRENT = "036"; + /** dex file version number for API level 13 and earlier */ + public static final String VERSION_FOR_API_13 = "035"; + /** + * value used to indicate endianness of file contents + */ + public static final int ENDIAN_TAG = 0x12345678; + /** + * Maximum addressable field or method index. + * The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or + * meth@CCCC. + */ + public static final int MAX_MEMBER_IDX = 0xFFFF; + /** + * Maximum addressable type index. + * The largest addressable type is 0xffff, in the "instruction formats" spec as type@CCCC. + */ + public static final int MAX_TYPE_IDX = 0xFFFF; + + private DexFormat() { } + + /** + * Returns the API level corresponding to the given magic number, + * or {@code -1} if the given array is not a well-formed dex file + * magic number. + */ + public static int magicToApi(byte[] magic) { + if (magic.length != 8) { + return -1; + } + + if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') + || (magic[7] != '\0')) { + return -1; + } + + String version = "" + ((char) magic[4]) + ((char) magic[5]) + ((char) magic[6]); + + if (version.equals(VERSION_CURRENT)) { + return API_CURRENT; + } else if (version.equals(VERSION_FOR_API_13)) { + return 13; + } + + return -1; + } + + /** + * Returns the magic number corresponding to the given target API level. + */ + public static String apiToMagic(int targetApiLevel) { + String version; + + if (targetApiLevel >= API_CURRENT) { + version = VERSION_CURRENT; + } else { + version = VERSION_FOR_API_13; + } + + return MAGIC_PREFIX + version + MAGIC_SUFFIX; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValue.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValue.java new file mode 100644 index 00000000..28105aca --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValue.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dex.util.CompareUtils; + +import static com.tencent.tinker.android.dex.TableOfContents.Section.Item; + +/** + * An encoded value or array. + */ +public final class EncodedValue extends Item { + public byte[] data; + + public EncodedValue(int off, byte[] data) { + super(off); + this.data = data; + } + + public ByteInput asByteInput() { + return new ByteInput() { + private int position = 0; + + @Override + public byte readByte() { + return data[position++]; + } + }; + } + + @Override public int compareTo(EncodedValue other) { + return CompareUtils.uArrCompare(data, other.data); + } + + @Override + public int byteCountInDex() { + return SizeOf.UBYTE * data.length; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueCodec.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueCodec.java new file mode 100644 index 00000000..0614c5d5 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueCodec.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dex.util.ByteOutput; + +/** + * Read and write {@code encoded_value} primitives. + */ +public final class EncodedValueCodec { + private EncodedValueCodec() { + } + + /** + * Writes a signed integral to {@code out}. + */ + public static void writeSignedIntegralValue(ByteOutput out, int type, long value) { + /* + * Figure out how many bits are needed to represent the value, + * including a sign bit: The bit count is subtracted from 65 + * and not 64 to account for the sign bit. The xor operation + * has the effect of leaving non-negative values alone and + * unary complementing negative values (so that a leading zero + * count always returns a useful number for our present + * purpose). + */ + int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Writes an unsigned integral to {@code out}. + */ + public static void writeUnsignedIntegralValue(ByteOutput out, int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfLeadingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Writes a right-zero-extended value to {@code out}. + */ + public static void writeRightZeroExtendedValue(ByteOutput out, int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfTrailingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + // Scootch the first bits to be written down to the low-order bits. + value >>= 64 - (requiredBytes * 8); + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Read a signed integer. + * + * @param zwidth byte count minus one + */ + public static int readSignedInt(ByteInput in, int zwidth) { + int result = 0; + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + result >>= (3 - zwidth) * 8; + return result; + } + + /** + * Read an unsigned integer. + * + * @param zwidth byte count minus one + * @param fillOnRight true to zero fill on the right; false on the left + */ + public static int readUnsignedInt(ByteInput in, int zwidth, boolean fillOnRight) { + int result = 0; + if (!fillOnRight) { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + result >>>= (3 - zwidth) * 8; + } else { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + } + return result; + } + + /** + * Read a signed long. + * + * @param zwidth byte count minus one + */ + public static long readSignedLong(ByteInput in, int zwidth) { + long result = 0; + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + result >>= (7 - zwidth) * 8; + return result; + } + + /** + * Read an unsigned long. + * + * @param zwidth byte count minus one + * @param fillOnRight true to zero fill on the right; false on the left + */ + public static long readUnsignedLong(ByteInput in, int zwidth, boolean fillOnRight) { + long result = 0; + if (!fillOnRight) { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + result >>>= (7 - zwidth) * 8; + } else { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + } + return result; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueReader.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueReader.java new file mode 100644 index 00000000..f6678b76 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueReader.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.ByteInput; + +/** + * Pull parser for encoded values. + */ +public final class EncodedValueReader { + public static final int ENCODED_BYTE = 0x00; + public static final int ENCODED_SHORT = 0x02; + public static final int ENCODED_CHAR = 0x03; + public static final int ENCODED_INT = 0x04; + public static final int ENCODED_LONG = 0x06; + public static final int ENCODED_FLOAT = 0x10; + public static final int ENCODED_DOUBLE = 0x11; + public static final int ENCODED_STRING = 0x17; + public static final int ENCODED_TYPE = 0x18; + public static final int ENCODED_FIELD = 0x19; + public static final int ENCODED_ENUM = 0x1b; + public static final int ENCODED_METHOD = 0x1a; + public static final int ENCODED_ARRAY = 0x1c; + public static final int ENCODED_ANNOTATION = 0x1d; + public static final int ENCODED_NULL = 0x1e; + public static final int ENCODED_BOOLEAN = 0x1f; + + /** placeholder type if the type is not yet known */ + private static final int MUST_READ = -1; + + protected final ByteInput in; + private int type = MUST_READ; + private int annotationType; + private int arg; + + public EncodedValueReader(ByteInput in) { + this.in = in; + } + + public EncodedValueReader(EncodedValue in) { + this(in.asByteInput()); + } + + /** + * Creates a new encoded value reader whose only value is the specified + * known type. This is useful for encoded values without a type prefix, + * such as class_def_item's encoded_array or annotation_item's + * encoded_annotation. + */ + public EncodedValueReader(ByteInput in, int knownType) { + this.in = in; + this.type = knownType; + } + + public EncodedValueReader(EncodedValue in, int knownType) { + this(in.asByteInput(), knownType); + } + + /** + * Returns the type of the next value to read. + */ + public int peek() { + if (type == MUST_READ) { + int argAndType = in.readByte() & 0xff; + type = argAndType & 0x1f; + arg = (argAndType & 0xe0) >> 5; + } + return type; + } + + /** + * Begins reading the elements of an array, returning the array's size. The + * caller must follow up by calling a read method for each element in the + * array. For example, this reads a byte array:
   {@code
+     *   int arraySize = readArray();
+     *   for (int i = 0, i < arraySize; i++) {
+     *     readByte();
+     *   }
+     * }
+ */ + public int readArray() { + checkType(ENCODED_ARRAY); + type = MUST_READ; + return Leb128.readUnsignedLeb128(in); + } + + /** + * Begins reading the fields of an annotation, returning the number of + * fields. The caller must follow up by making alternating calls to {@link + * #readAnnotationName()} and another read method. For example, this reads + * an annotation whose fields are all bytes:
   {@code
+     *   int fieldCount = readAnnotation();
+     *   int annotationType = getAnnotationType();
+     *   for (int i = 0; i < fieldCount; i++) {
+     *       readAnnotationName();
+     *       readByte();
+     *   }
+     * }
+ */ + public int readAnnotation() { + checkType(ENCODED_ANNOTATION); + type = MUST_READ; + annotationType = Leb128.readUnsignedLeb128(in); + return Leb128.readUnsignedLeb128(in); + } + + /** + * Returns the type of the annotation just returned by {@link + * #readAnnotation()}. This method's value is undefined unless the most + * recent call was to {@link #readAnnotation()}. + */ + public int getAnnotationType() { + return annotationType; + } + + public int readAnnotationName() { + return Leb128.readUnsignedLeb128(in); + } + + public byte readByte() { + checkType(ENCODED_BYTE); + type = MUST_READ; + return (byte) EncodedValueCodec.readSignedInt(in, arg); + } + + public short readShort() { + checkType(ENCODED_SHORT); + type = MUST_READ; + return (short) EncodedValueCodec.readSignedInt(in, arg); + } + + public char readChar() { + checkType(ENCODED_CHAR); + type = MUST_READ; + return (char) EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readInt() { + checkType(ENCODED_INT); + type = MUST_READ; + return EncodedValueCodec.readSignedInt(in, arg); + } + + public long readLong() { + checkType(ENCODED_LONG); + type = MUST_READ; + return EncodedValueCodec.readSignedLong(in, arg); + } + + public float readFloat() { + checkType(ENCODED_FLOAT); + type = MUST_READ; + return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true)); + } + + public double readDouble() { + checkType(ENCODED_DOUBLE); + type = MUST_READ; + return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true)); + } + + public int readString() { + checkType(ENCODED_STRING); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readType() { + checkType(ENCODED_TYPE); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readField() { + checkType(ENCODED_FIELD); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readEnum() { + checkType(ENCODED_ENUM); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readMethod() { + checkType(ENCODED_METHOD); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public void readNull() { + checkType(ENCODED_NULL); + type = MUST_READ; + } + + public boolean readBoolean() { + checkType(ENCODED_BOOLEAN); + type = MUST_READ; + return arg != 0; + } + + /** + * Skips a single value, including its nested values if it is an array or + * annotation. + */ + public void skipValue() { + switch (peek()) { + case ENCODED_BYTE: + readByte(); + break; + case ENCODED_SHORT: + readShort(); + break; + case ENCODED_CHAR: + readChar(); + break; + case ENCODED_INT: + readInt(); + break; + case ENCODED_LONG: + readLong(); + break; + case ENCODED_FLOAT: + readFloat(); + break; + case ENCODED_DOUBLE: + readDouble(); + break; + case ENCODED_STRING: + readString(); + break; + case ENCODED_TYPE: + readType(); + break; + case ENCODED_FIELD: + readField(); + break; + case ENCODED_ENUM: + readEnum(); + break; + case ENCODED_METHOD: + readMethod(); + break; + case ENCODED_ARRAY: + for (int i = 0, size = readArray(); i < size; i++) { + skipValue(); + } + break; + case ENCODED_ANNOTATION: + for (int i = 0, size = readAnnotation(); i < size; i++) { + readAnnotationName(); + skipValue(); + } + break; + case ENCODED_NULL: + readNull(); + break; + case ENCODED_BOOLEAN: + readBoolean(); + break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(type)); + } + } + + private void checkType(int expected) { + if (peek() != expected) { + throw new IllegalStateException( + String.format("Expected %x but was %x", expected, peek())); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/FieldId.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/FieldId.java new file mode 100644 index 00000000..40435348 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/FieldId.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class FieldId extends Item { + public int declaringClassIndex; + public int typeIndex; + public int nameIndex; + + public FieldId(int off, int declaringClassIndex, int typeIndex, int nameIndex) { + super(off); + this.declaringClassIndex = declaringClassIndex; + this.typeIndex = typeIndex; + this.nameIndex = nameIndex; + } + + public int compareTo(FieldId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return CompareUtils.uCompare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return CompareUtils.uCompare(nameIndex, other.nameIndex); + } + return CompareUtils.uCompare(typeIndex, other.typeIndex); // should always be 0 + } + + @Override + public int byteCountInDex() { + return SizeOf.MEMBER_ID_ITEM; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Leb128.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Leb128.java new file mode 100644 index 00000000..13ecd9c8 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Leb128.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dex.util.ByteOutput; + +/** + * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 + * section 7.6. + */ +public final class Leb128 { + private Leb128() { + } + + /** + * Gets the number of bytes in the unsigned LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int unsignedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >>> 7; + int count = 0; + + while (remaining != 0) { + remaining >>>= 7; + count++; + } + + return count + 1; + } + + public static int unsignedLeb128p1Size(int value) { + return unsignedLeb128Size(value + 1); + } + + /** + * Gets the number of bytes in the signed LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int signedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + value = remaining; + remaining >>= 7; + count++; + } + + return count; + } + + /** + * Reads an signed integer from {@code in}. + */ + public static int readSignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0) { + result |= signBits; + } + + return result; + } + + /** + * Reads an unsigned leb128 integer from {@code in}. + */ + public static int readUnsignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + return result; + } + + /** + * Reads an unsigned leb128p1 integer from {@code in}. + */ + public static int readUnsignedLeb128p1(ByteInput in) { + return readUnsignedLeb128(in) - 1; + } + + /** + * Writes {@code value} as an unsigned leb128 integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static int writeUnsignedLeb128(ByteOutput out, int value) { + int remaining = value >>> 7; + int bytesWritten = 0; + while (remaining != 0) { + out.writeByte((byte) ((value & 0x7f) | 0x80)); + ++bytesWritten; + value = remaining; + remaining >>>= 7; + } + + out.writeByte((byte) (value & 0x7f)); + ++bytesWritten; + + return bytesWritten; + } + + /** + * Writes {@code value} as an unsigned integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static int writeUnsignedLeb128p1(ByteOutput out, int value) { + return writeUnsignedLeb128(out, value + 1); + } + + /** + * Writes {@code value} as a signed integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static int writeSignedLeb128(ByteOutput out, int value) { + int remaining = value >> 7; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + int bytesWritten = 0; + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0))); + ++bytesWritten; + value = remaining; + remaining >>= 7; + } + + return bytesWritten; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/MethodId.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/MethodId.java new file mode 100644 index 00000000..fcb71ca9 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/MethodId.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class MethodId extends Item { + public int declaringClassIndex; + public int protoIndex; + public int nameIndex; + + public MethodId(int off, int declaringClassIndex, int protoIndex, int nameIndex) { + super(off); + this.declaringClassIndex = declaringClassIndex; + this.protoIndex = protoIndex; + this.nameIndex = nameIndex; + } + + public int compareTo(MethodId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return CompareUtils.uCompare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return CompareUtils.uCompare(nameIndex, other.nameIndex); + } + return CompareUtils.uCompare(protoIndex, other.protoIndex); + } + + @Override + public int byteCountInDex() { + return SizeOf.MEMBER_ID_ITEM; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Mutf8.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Mutf8.java new file mode 100644 index 00000000..f600495e --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Mutf8.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.ByteInput; +import java.io.UTFDataFormatException; + +/** + * Modified UTF-8 as described in the dex file format spec. + * + *

Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8. + */ +public final class Mutf8 { + private Mutf8() { } + + /** + * Decodes bytes from {@code in} into {@code out} until a delimiter 0x00 is + * encountered. Returns a new string containing the decoded characters. + */ + public static String decode(ByteInput in, char[] out) throws UTFDataFormatException { + int s = 0; + while (true) { + char a = (char) (in.readByte() & 0xff); + if (a == 0) { + return new String(out, 0, s); + } + out[s] = a; + if (a < '\u0080') { + s++; + } else if ((a & 0xe0) == 0xc0) { + int b = in.readByte() & 0xff; + if ((b & 0xC0) != 0x80) { + throw new UTFDataFormatException("bad second byte"); + } + out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + } else if ((a & 0xf0) == 0xe0) { + int b = in.readByte() & 0xff; + int c = in.readByte() & 0xff; + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new UTFDataFormatException("bad second or third byte"); + } + out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + } else { + throw new UTFDataFormatException("bad byte"); + } + } + } + + /** + * Returns the number of bytes the modified UTF8 representation of 's' would take. + */ + public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException { + long result = 0; + final int length = s.length(); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + ++result; + } else if (ch <= 2047) { + result += 2; + } else { + result += 3; + } + if (shortLength && result > 65535) { + throw new UTFDataFormatException("String more than 65535 UTF bytes long"); + } + } + return result; + } + + /** + * Encodes the modified UTF-8 bytes corresponding to {@code s} into {@code + * dst}, starting at {@code offset}. + */ + public static void encode(byte[] dst, int offset, String s) { + final int length = s.length(); + for (int i = 0; i < length; i++) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + dst[offset++] = (byte) ch; + } else if (ch <= 2047) { + dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } else { + dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12))); + dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } + } + } + + /** + * Returns an array containing the modified UTF-8 form of {@code s}. + */ + public static byte[] encode(String s) throws UTFDataFormatException { + int utfCount = (int) countBytes(s, true); + byte[] result = new byte[utfCount]; + encode(result, 0, s); + return result; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ProtoId.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ProtoId.java new file mode 100644 index 00000000..c5a6bbe4 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ProtoId.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class ProtoId extends TableOfContents.Section.Item { + public int shortyIndex; + public int returnTypeIndex; + public int parametersOffset; + + public ProtoId(int off, int shortyIndex, int returnTypeIndex, int parametersOffset) { + super(off); + this.shortyIndex = shortyIndex; + this.returnTypeIndex = returnTypeIndex; + this.parametersOffset = parametersOffset; + } + + public int compareTo(ProtoId other) { + int res = CompareUtils.uCompare(shortyIndex, other.shortyIndex); + if (res != 0) { + return res; + } + res = CompareUtils.uCompare(returnTypeIndex, other.returnTypeIndex); + if (res != 0) { + return res; + } + return CompareUtils.sCompare(parametersOffset, other.parametersOffset); + } + + + @Override + public int byteCountInDex() { + return SizeOf.PROTO_ID_ITEM; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/SizeOf.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/SizeOf.java new file mode 100644 index 00000000..52d6ba34 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/SizeOf.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +public final class SizeOf { + public static final int UBYTE = 1; + public static final int USHORT = 2; + public static final int UINT = 4; + public static final int SIGNATURE = UBYTE * 20; + public static final int CHECKSUM = UBYTE * 4; + /** + * magic ubyte[8] + * checksum uint + * signature ubyte[20] + * file_size uint + * header_size uint + * endian_tag uint + * link_size uint + * link_off uint + * map_off uint + * string_ids_size uint + * string_ids_off uint + * type_ids_size uint + * type_ids_off uint + * proto_ids_size uint + * proto_ids_off uint + * field_ids_size uint + * field_ids_off uint + * method_ids_size uint + * method_ids_off uint + * class_defs_size uint + * class_defs_off uint + * data_size uint + * data_off uint + */ + public static final int HEADER_ITEM = (8 * UBYTE) + UINT + SIGNATURE + (20 * UINT); // 0x70 + /** + * string_data_off uint + */ + public static final int STRING_ID_ITEM = UINT; + /** + * descriptor_idx uint + */ + public static final int TYPE_ID_ITEM = UINT; + /** + * type_idx ushort + */ + public static final int TYPE_ITEM = USHORT; + /** + * shorty_idx uint + * return_type_idx uint + * return_type_idx uint + */ + public static final int PROTO_ID_ITEM = UINT + UINT + UINT; + /** + * class_idx ushort + * type_idx/proto_idx ushort + * name_idx uint + */ + public static final int MEMBER_ID_ITEM = USHORT + USHORT + UINT; + /** + * class_idx uint + * access_flags uint + * superclass_idx uint + * interfaces_off uint + * source_file_idx uint + * annotations_off uint + * class_data_off uint + * static_values_off uint + */ + public static final int CLASS_DEF_ITEM = 8 * UINT; + /** + * type ushort + * unused ushort + * size uint + * offset uint + */ + public static final int MAP_ITEM = USHORT + USHORT + UINT + UINT; + /** + * start_addr uint + * insn_count ushort + * handler_off ushort + */ + public static final int TRY_ITEM = UINT + USHORT + USHORT; + + private SizeOf() { } + + public static int roundToTimesOfFour(int value) { + return (value + 3) & (~3); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/StringData.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/StringData.java new file mode 100644 index 00000000..1f7848e0 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/StringData.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; + +import java.io.UTFDataFormatException; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Structure of StringData element in Dex file. + */ +public class StringData extends Item { + public String value; + + public StringData(int offset, String value) { + super(offset); + this.value = value; + } + + @Override + public int compareTo(StringData other) { + return value.compareTo(other.value); + } + + @Override + public int byteCountInDex() { + try { + return Leb128.unsignedLeb128Size(value.length()) + (int) Mutf8.countBytes(value, true) + SizeOf.UBYTE; + } catch (UTFDataFormatException e) { + throw new DexException(e); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TableOfContents.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TableOfContents.java new file mode 100644 index 00000000..7ad5e415 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TableOfContents.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * The file header and map. + */ +public final class TableOfContents { + public static final short SECTION_TYPE_HEADER = 0x0000; + public static final short SECTION_TYPE_STRINGIDS = 0x0001; + public static final short SECTION_TYPE_TYPEIDS = 0x0002; + public static final short SECTION_TYPE_PROTOIDS = 0x0003; + public static final short SECTION_TYPE_FIELDIDS = 0x0004; + public static final short SECTION_TYPE_METHODIDS = 0x0005; + public static final short SECTION_TYPE_CLASSDEFS = 0x0006; + public static final short SECTION_TYPE_MAPLIST = 0x1000; + public static final short SECTION_TYPE_TYPELISTS = 0x1001; + public static final short SECTION_TYPE_ANNOTATIONSETREFLISTS = 0x1002; + public static final short SECTION_TYPE_ANNOTATIONSETS = 0x1003; + public static final short SECTION_TYPE_CLASSDATA = 0x2000; + public static final short SECTION_TYPE_CODES = 0x2001; + public static final short SECTION_TYPE_STRINGDATAS = 0x2002; + public static final short SECTION_TYPE_DEBUGINFOS = 0x2003; + public static final short SECTION_TYPE_ANNOTATIONS = 0x2004; + public static final short SECTION_TYPE_ENCODEDARRAYS = 0x2005; + public static final short SECTION_TYPE_ANNOTATIONSDIRECTORIES = 0x2006; + + public final Section header = new Section(SECTION_TYPE_HEADER, true); + public final Section stringIds = new Section(SECTION_TYPE_STRINGIDS, true); + public final Section typeIds = new Section(SECTION_TYPE_TYPEIDS, true); + public final Section protoIds = new Section(SECTION_TYPE_PROTOIDS, true); + public final Section fieldIds = new Section(SECTION_TYPE_FIELDIDS, true); + public final Section methodIds = new Section(SECTION_TYPE_METHODIDS, true); + public final Section classDefs = new Section(SECTION_TYPE_CLASSDEFS, true); + public final Section mapList = new Section(SECTION_TYPE_MAPLIST, true); + public final Section typeLists = new Section(SECTION_TYPE_TYPELISTS, true); + public final Section annotationSetRefLists = new Section(SECTION_TYPE_ANNOTATIONSETREFLISTS, true); + public final Section annotationSets = new Section(SECTION_TYPE_ANNOTATIONSETS, true); + public final Section classDatas = new Section(SECTION_TYPE_CLASSDATA, false); + public final Section codes = new Section(SECTION_TYPE_CODES, true); + public final Section stringDatas = new Section(SECTION_TYPE_STRINGDATAS, false); + public final Section debugInfos = new Section(SECTION_TYPE_DEBUGINFOS, false); + public final Section annotations = new Section(SECTION_TYPE_ANNOTATIONS, false); + public final Section encodedArrays = new Section(SECTION_TYPE_ENCODEDARRAYS, false); + public final Section annotationsDirectories = new Section(SECTION_TYPE_ANNOTATIONSDIRECTORIES, true); + public final Section[] sections = { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList, + typeLists, annotationSetRefLists, annotationSets, classDatas, codes, stringDatas, + debugInfos, annotations, encodedArrays, annotationsDirectories + }; + + public int checksum; + public byte[] signature; + public int fileSize; + public int linkSize; + public int linkOff; + public int dataSize; + public int dataOff; + + public TableOfContents() { + signature = new byte[20]; + } + + public Section getSectionByType(int type) { + switch (type) { + case SECTION_TYPE_HEADER: { + return header; + } + case SECTION_TYPE_STRINGIDS: { + return stringIds; + } + case SECTION_TYPE_TYPEIDS: { + return typeIds; + } + case SECTION_TYPE_PROTOIDS: { + return protoIds; + } + case SECTION_TYPE_FIELDIDS: { + return fieldIds; + } + case SECTION_TYPE_METHODIDS: { + return methodIds; + } + case SECTION_TYPE_CLASSDEFS: { + return classDefs; + } + case SECTION_TYPE_MAPLIST: { + return mapList; + } + case SECTION_TYPE_TYPELISTS: { + return typeLists; + } + case SECTION_TYPE_ANNOTATIONSETREFLISTS: { + return annotationSetRefLists; + } + case SECTION_TYPE_ANNOTATIONSETS: { + return annotationSets; + } + case SECTION_TYPE_CLASSDATA: { + return classDatas; + } + case SECTION_TYPE_CODES: { + return codes; + } + case SECTION_TYPE_STRINGDATAS: { + return stringDatas; + } + case SECTION_TYPE_DEBUGINFOS: { + return debugInfos; + } + case SECTION_TYPE_ANNOTATIONS: { + return annotations; + } + case SECTION_TYPE_ENCODEDARRAYS: { + return encodedArrays; + } + case SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + return annotationsDirectories; + } + default: { + throw new IllegalArgumentException("unknown section type: " + type); + } + } + } + + public void readFrom(Dex dex) throws IOException { + readHeader(dex.openSection(header)); + // special case, since mapList.byteCount is available only after + // computeSizesFromOffsets() was invoked, so here we can't use + // dex.openSection(mapList) to get dex section. Or + // an {@code java.nio.BufferUnderflowException} will be thrown. + readMap(dex.openSection(mapList.off)); + computeSizesFromOffsets(); + } + + private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException { + byte[] magic = headerIn.readByteArray(8); + int apiTarget = DexFormat.magicToApi(magic); + + if (apiTarget != DexFormat.API_NO_EXTENDED_OPCODES) { + throw new DexException("Unexpected magic: " + Arrays.toString(magic)); + } + + checksum = headerIn.readInt(); + signature = headerIn.readByteArray(20); + fileSize = headerIn.readInt(); + int headerSize = headerIn.readInt(); + if (headerSize != SizeOf.HEADER_ITEM) { + throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize)); + } + int endianTag = headerIn.readInt(); + if (endianTag != DexFormat.ENDIAN_TAG) { + throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag)); + } + linkSize = headerIn.readInt(); + linkOff = headerIn.readInt(); + mapList.off = headerIn.readInt(); + if (mapList.off == 0) { + throw new DexException("Cannot merge dex files that do not contain a map"); + } + stringIds.size = headerIn.readInt(); + stringIds.off = headerIn.readInt(); + typeIds.size = headerIn.readInt(); + typeIds.off = headerIn.readInt(); + protoIds.size = headerIn.readInt(); + protoIds.off = headerIn.readInt(); + fieldIds.size = headerIn.readInt(); + fieldIds.off = headerIn.readInt(); + methodIds.size = headerIn.readInt(); + methodIds.off = headerIn.readInt(); + classDefs.size = headerIn.readInt(); + classDefs.off = headerIn.readInt(); + dataSize = headerIn.readInt(); + dataOff = headerIn.readInt(); + } + + private void readMap(Dex.Section in) throws IOException { + int mapSize = in.readInt(); + Section previous = null; + for (int i = 0; i < mapSize; i++) { + short type = in.readShort(); + in.readShort(); // unused + Section section = getSection(type); + int size = in.readInt(); + int offset = in.readInt(); + + if ((section.size != 0 && section.size != size) + || (section.off != Section.UNDEF_OFFSET && section.off != offset)) { + throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type)); + } + + section.size = size; + section.off = offset; + + if (previous != null && previous.off > section.off) { + throw new DexException("Map is unsorted at " + previous + ", " + section); + } + + previous = section; + } + + header.off = 0; + + Arrays.sort(sections); + + // Skip header section, since its offset must be zero. + for (int i = 1; i < sections.length; ++i) { + if (sections[i].off == Section.UNDEF_OFFSET) { + sections[i].off = sections[i - 1].off; + } + } + } + + public void computeSizesFromOffsets() { + int end = fileSize; + for (int i = sections.length - 1; i >= 0; i--) { + Section section = sections[i]; + if (section.off == Section.UNDEF_OFFSET) { + continue; + } + if (section.off > end) { + throw new DexException("Map is unsorted at " + section); + } + section.byteCount = end - section.off; + end = section.off; + } + + dataOff = header.byteCount + + stringIds.byteCount + + typeIds.byteCount + + protoIds.byteCount + + fieldIds.byteCount + + methodIds.byteCount + + classDefs.byteCount; + + dataSize = fileSize - dataOff; + } + + private Section getSection(short type) { + for (Section section : sections) { + if (section.type == type) { + return section; + } + } + throw new IllegalArgumentException("No such map item: " + type); + } + + public void writeHeader(Dex.Section out) throws IOException { + out.write(DexFormat.apiToMagic(DexFormat.API_NO_EXTENDED_OPCODES).getBytes("UTF-8")); + out.writeInt(checksum); + out.write(signature); + out.writeInt(fileSize); + out.writeInt(SizeOf.HEADER_ITEM); + out.writeInt(DexFormat.ENDIAN_TAG); + out.writeInt(linkSize); + out.writeInt(linkOff); + out.writeInt(mapList.off); + out.writeInt(stringIds.size); + out.writeInt((stringIds.exists() ? stringIds.off : 0)); + out.writeInt(typeIds.size); + out.writeInt((typeIds.exists() ? typeIds.off : 0)); + out.writeInt(protoIds.size); + out.writeInt((protoIds.exists() ? protoIds.off : 0)); + out.writeInt(fieldIds.size); + out.writeInt((fieldIds.exists() ? fieldIds.off : 0)); + out.writeInt(methodIds.size); + out.writeInt((methodIds.exists() ? methodIds.off : 0)); + out.writeInt(classDefs.size); + out.writeInt((classDefs.exists() ? classDefs.off : 0)); + out.writeInt(dataSize); + out.writeInt(dataOff); + } + + public void writeMap(Dex.Section out) throws IOException { + int count = 0; + for (Section section : sections) { + if (section.exists()) { + count++; + } + } + + out.writeInt(count); + for (Section section : sections) { + if (section.exists()) { + out.writeShort(section.type); + out.writeShort((short) 0); + out.writeInt(section.size); + out.writeInt(section.off); + } + } + } + + public static class Section implements Comparable

{ + public static final int UNDEF_INDEX = -1; + public static final int UNDEF_OFFSET = -1; + public final short type; + public boolean isElementFourByteAligned; + public int size = 0; + public int off = UNDEF_OFFSET; + public int byteCount = 0; + + public Section(int type, boolean isElementFourByteAligned) { + this.type = (short) type; + this.isElementFourByteAligned = isElementFourByteAligned; + if (type == SECTION_TYPE_HEADER) { + off = 0; + size = 1; + byteCount = SizeOf.HEADER_ITEM; + } else + if (type == SECTION_TYPE_MAPLIST) { + size = 1; + } + } + + public boolean exists() { + return size > 0; + } + + private int remapTypeOrderId(int type) { + switch (type) { + case SECTION_TYPE_HEADER: { + return 0; + } + case SECTION_TYPE_STRINGIDS: { + return 1; + } + case SECTION_TYPE_TYPEIDS: { + return 2; + } + case SECTION_TYPE_PROTOIDS: { + return 3; + } + case SECTION_TYPE_FIELDIDS: { + return 4; + } + case SECTION_TYPE_METHODIDS: { + return 5; + } + case SECTION_TYPE_CLASSDEFS: { + return 6; + } + case SECTION_TYPE_STRINGDATAS: { + return 7; + } + case SECTION_TYPE_TYPELISTS: { + return 8; + } + case SECTION_TYPE_ANNOTATIONS: { + return 9; + } + case SECTION_TYPE_ANNOTATIONSETS: { + return 10; + } + case SECTION_TYPE_ANNOTATIONSETREFLISTS: { + return 11; + } + case SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + return 12; + } + case SECTION_TYPE_DEBUGINFOS: { + return 13; + } + case SECTION_TYPE_CODES: { + return 14; + } + case SECTION_TYPE_CLASSDATA: { + return 15; + } + case SECTION_TYPE_ENCODEDARRAYS: { + return 16; + } + case SECTION_TYPE_MAPLIST: { + return 17; + } + default: { + throw new IllegalArgumentException("unknown section type: " + type); + } + } + } + + public int compareTo(Section section) { + if (off != section.off) { + return off < section.off ? -1 : 1; + } + + int remappedType = remapTypeOrderId(type); + int otherRemappedType = remapTypeOrderId(section.type); + + if (remappedType != otherRemappedType) { + return (remappedType < otherRemappedType ? -1 : 1); + } + + return 0; + } + + @Override + public String toString() { + return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size); + } + + public static abstract class Item implements Comparable { + public int off; + + public Item(int off) { + this.off = off; + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + return compareTo((T) obj) == 0; + } + + public abstract int byteCountInDex(); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TypeList.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TypeList.java new file mode 100644 index 00000000..f10d5db4 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TypeList.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex; + +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.util.CompareUtils; + +public final class TypeList extends Item { + public static final TypeList EMPTY = new TypeList(0, Dex.EMPTY_SHORT_ARRAY); + + public short[] types; + + public TypeList(int off, short[] types) { + super(off); + this.types = types; + } + + @Override public int compareTo(TypeList other) { + return CompareUtils.uArrCompare(types, other.types); + } + + @Override + public int byteCountInDex() { + return SizeOf.UINT + types.length * SizeOf.USHORT; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java new file mode 100644 index 00000000..f2e16ed6 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex.io; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.EncodedValueReader; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.Leb128; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.Mutf8; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dex.util.ByteOutput; + +import java.io.ByteArrayOutputStream; +import java.io.UTFDataFormatException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * *** This file is NOT a part of AOSP. *** + * Created by tangyinsheng on 2016/6/30. + */ +public class DexDataBuffer implements ByteInput, ByteOutput { + public static final int DEFAULT_BUFFER_SIZE = 512; + + private static final short[] EMPTY_SHORT_ARRAY = new short[0]; + + private ByteBuffer data; + private int dataBound; + private boolean isResizeAllowed; + + public DexDataBuffer() { + this.data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.dataBound = this.data.position(); + this.data.limit(this.data.capacity()); + this.isResizeAllowed = true; + } + + public DexDataBuffer(ByteBuffer data) { + this.data = data; + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.dataBound = data.limit(); + this.isResizeAllowed = false; + } + + public DexDataBuffer(ByteBuffer data, boolean isResizeAllowed) { + this.data = data; + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.dataBound = data.limit(); + this.isResizeAllowed = isResizeAllowed; + } + + public int position() { + return data.position(); + } + + public void position(int pos) { + data.position(pos); + } + + public int available() { + return dataBound - data.position(); + } + + private void ensureBufferSize(int bytes) { + if (this.data.position() + bytes > this.data.limit()) { + if (this.isResizeAllowed) { + byte[] array = this.data.array(); + byte[] newArray = new byte[array.length + bytes + (array.length >> 1)]; + System.arraycopy(array, 0, newArray, 0, this.data.position()); + int lastPos = this.data.position(); + this.data = ByteBuffer.wrap(newArray); + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.data.position(lastPos); + this.data.limit(this.data.capacity()); + } + } + } + + public byte[] array() { + byte[] result = new byte[this.dataBound]; + byte[] dataArray = this.data.array(); + System.arraycopy(dataArray, 0, result, 0, this.dataBound); + return result; + } + + @Override + public byte readByte() { + return data.get(); + } + + public int readUnsignedByte() { + return readByte() & 0xFF; + } + + public short readShort() { + return data.getShort(); + } + + public int readUnsignedShort() { + return readShort() & 0xffff; + } + + public int readInt() { + return data.getInt(); + } + + public byte[] readByteArray(int length) { + byte[] result = new byte[length]; + data.get(result); + return result; + } + + public short[] readShortArray(int length) { + if (length == 0) { + return EMPTY_SHORT_ARRAY; + } + short[] result = new short[length]; + for (int i = 0; i < length; i++) { + result[i] = readShort(); + } + return result; + } + + public int readUleb128() { + return Leb128.readUnsignedLeb128(this); + } + + public int readUleb128p1() { + return Leb128.readUnsignedLeb128(this) - 1; + } + + public int readSleb128() { + return Leb128.readSignedLeb128(this); + } + + public StringData readStringData() { + int off = data.position(); + try { + int expectedLength = readUleb128(); + String result = Mutf8.decode(this, new char[expectedLength]); + if (result.length() != expectedLength) { + throw new DexException("Declared length " + expectedLength + + " doesn't match decoded length of " + result.length()); + } + return new StringData(off, result); + } catch (UTFDataFormatException e) { + throw new DexException(e); + } + } + + public TypeList readTypeList() { + int off = data.position(); + int size = readInt(); + short[] types = readShortArray(size); + return new TypeList(off, types); + } + + public FieldId readFieldId() { + int off = data.position(); + int declaringClassIndex = readUnsignedShort(); + int typeIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new FieldId(off, declaringClassIndex, typeIndex, nameIndex); + } + + public MethodId readMethodId() { + int off = data.position(); + int declaringClassIndex = readUnsignedShort(); + int protoIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new MethodId(off, declaringClassIndex, protoIndex, nameIndex); + } + + public ProtoId readProtoId() { + int off = data.position(); + int shortyIndex = readInt(); + int returnTypeIndex = readInt(); + int parametersOffset = readInt(); + return new ProtoId(off, shortyIndex, returnTypeIndex, parametersOffset); + } + + public ClassDef readClassDef() { + int off = position(); + int type = readInt(); + int accessFlags = readInt(); + int supertype = readInt(); + int interfacesOffset = readInt(); + int sourceFileIndex = readInt(); + int annotationsOffset = readInt(); + int classDataOffset = readInt(); + int staticValuesOffset = readInt(); + return new ClassDef(off, type, accessFlags, supertype, + interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, + staticValuesOffset); + } + + public Code readCode() { + int off = data.position(); + int registersSize = readUnsignedShort(); + int insSize = readUnsignedShort(); + int outsSize = readUnsignedShort(); + int triesSize = readUnsignedShort(); + int debugInfoOffset = readInt(); + int instructionsSize = readInt(); + short[] instructions = readShortArray(instructionsSize); + Code.Try[] tries; + Code.CatchHandler[] catchHandlers; + if (triesSize > 0) { + if (instructions.length % 2 == 1) { + readShort(); // padding + } + + /* + * We can't read the tries until we've read the catch handlers. + * Unfortunately they're in the opposite order in the dex file + * so we need to read them out-of-order. + */ + int posBeforeTries = data.position(); + skip(triesSize * SizeOf.TRY_ITEM); + catchHandlers = readCatchHandlers(); + int posAfterCatchHandlers = data.position(); + data.position(posBeforeTries); + tries = readTries(triesSize, catchHandlers); + data.position(posAfterCatchHandlers); + } else { + tries = new Code.Try[0]; + catchHandlers = new Code.CatchHandler[0]; + } + return new Code(off, registersSize, insSize, outsSize, debugInfoOffset, instructions, + tries, catchHandlers); + } + + private Code.CatchHandler[] readCatchHandlers() { + int baseOffset = data.position(); + int catchHandlersSize = readUleb128(); + Code.CatchHandler[] result = new Code.CatchHandler[catchHandlersSize]; + for (int i = 0; i < catchHandlersSize; i++) { + int offset = data.position() - baseOffset; + result[i] = readCatchHandler(offset); + } + return result; + } + + private Code.Try[] readTries(int triesSize, Code.CatchHandler[] catchHandlers) { + Code.Try[] result = new Code.Try[triesSize]; + for (int i = 0; i < triesSize; i++) { + int startAddress = readInt(); + int instructionCount = readUnsignedShort(); + int handlerOffset = readUnsignedShort(); + int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); + result[i] = new Code.Try(startAddress, instructionCount, catchHandlerIndex); + } + return result; + } + + private int findCatchHandlerIndex(Code.CatchHandler[] catchHandlers, int offset) { + for (int i = 0; i < catchHandlers.length; i++) { + Code.CatchHandler catchHandler = catchHandlers[i]; + if (catchHandler.offset == offset) { + return i; + } + } + throw new IllegalArgumentException(); + } + + private Code.CatchHandler readCatchHandler(int offset) { + int size = readSleb128(); + int handlersCount = Math.abs(size); + int[] typeIndexes = new int[handlersCount]; + int[] addresses = new int[handlersCount]; + for (int i = 0; i < handlersCount; i++) { + typeIndexes[i] = readUleb128(); + addresses[i] = readUleb128(); + } + int catchAllAddress = size <= 0 ? readUleb128() : -1; + return new Code.CatchHandler(typeIndexes, addresses, catchAllAddress, offset); + } + + public DebugInfoItem readDebugInfoItem() { + int off = data.position(); + + int lineStart = readUleb128(); + int parametersSize = readUleb128(); + int[] parameterNames = new int[parametersSize]; + for (int i = 0; i < parametersSize; ++i) { + parameterNames[i] = readUleb128p1(); + } + + ByteArrayOutputStream baos = null; + + try { + baos = new ByteArrayOutputStream(64); + + final ByteArrayOutputStream baosRef = baos; + + ByteOutput outAdapter = new ByteOutput() { + @Override + public void writeByte(int i) { + baosRef.write(i); + } + }; + + outside_whileloop: + while (true) { + int opcode = readByte(); + baos.write(opcode); + switch (opcode) { + case DebugInfoItem.DBG_END_SEQUENCE: { + break outside_whileloop; + } + case DebugInfoItem.DBG_ADVANCE_PC: { + int addrDiff = readUleb128(); + Leb128.writeUnsignedLeb128(outAdapter, addrDiff); + break; + } + case DebugInfoItem.DBG_ADVANCE_LINE: { + int lineDiff = readSleb128(); + Leb128.writeSignedLeb128(outAdapter, lineDiff); + break; + } + case DebugInfoItem.DBG_START_LOCAL: + case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { + int registerNum = readUleb128(); + Leb128.writeUnsignedLeb128(outAdapter, registerNum); + int nameIndex = readUleb128p1(); + Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); + int typeIndex = readUleb128p1(); + Leb128.writeUnsignedLeb128p1(outAdapter, typeIndex); + if (opcode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { + int sigIndex = readUleb128p1(); + Leb128.writeUnsignedLeb128p1(outAdapter, sigIndex); + } + break; + } + case DebugInfoItem.DBG_END_LOCAL: + case DebugInfoItem.DBG_RESTART_LOCAL: { + int registerNum = readUleb128(); + Leb128.writeUnsignedLeb128(outAdapter, registerNum); + break; + } + case DebugInfoItem.DBG_SET_FILE: { + int nameIndex = readUleb128p1(); + Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); + break; + } + case DebugInfoItem.DBG_SET_PROLOGUE_END: + case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: + default: { + break; + } + } + } + + byte[] infoSTM = baos.toByteArray(); + return new DebugInfoItem(off, lineStart, parameterNames, infoSTM); + } finally { + if (baos != null) { + try { + baos.close(); + } catch (Exception e) { + // Do nothing. + } + } + } + } + + public ClassData readClassData() { + int off = data.position(); + int staticFieldsSize = readUleb128(); + int instanceFieldsSize = readUleb128(); + int directMethodsSize = readUleb128(); + int virtualMethodsSize = readUleb128(); + ClassData.Field[] staticFields = readFields(staticFieldsSize); + ClassData.Field[] instanceFields = readFields(instanceFieldsSize); + ClassData.Method[] directMethods = readMethods(directMethodsSize); + ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); + return new ClassData(off, staticFields, instanceFields, directMethods, virtualMethods); + } + + private ClassData.Field[] readFields(int count) { + ClassData.Field[] result = new ClassData.Field[count]; + int fieldIndex = 0; + for (int i = 0; i < count; i++) { + fieldIndex += readUleb128(); // field index diff + int accessFlags = readUleb128(); + result[i] = new ClassData.Field(fieldIndex, accessFlags); + } + return result; + } + + private ClassData.Method[] readMethods(int count) { + ClassData.Method[] result = new ClassData.Method[count]; + int methodIndex = 0; + for (int i = 0; i < count; i++) { + methodIndex += readUleb128(); // method index diff + int accessFlags = readUleb128(); + int codeOff = readUleb128(); + result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); + } + return result; + } + + /** + * Returns a byte array containing the bytes from {@code start} to this + * section's current position. + */ + private byte[] getBytesFrom(int start) { + int end = data.position(); + byte[] result = new byte[end - start]; + data.position(start); + data.get(result); + return result; + } + + public Annotation readAnnotation() { + int off = data.position(); + byte visibility = readByte(); + int start = data.position(); + new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue(); + return new Annotation(off, visibility, new EncodedValue(start, getBytesFrom(start))); + } + + public AnnotationSet readAnnotationSet() { + int off = data.position(); + int size = readInt(); + int[] annotationOffsets = new int[size]; + for (int i = 0; i < size; ++i) { + annotationOffsets[i] = readInt(); + } + return new AnnotationSet(off, annotationOffsets); + } + + public AnnotationSetRefList readAnnotationSetRefList() { + int off = data.position(); + int size = readInt(); + int[] annotationSetRefItems = new int[size]; + for (int i = 0; i < size; ++i) { + annotationSetRefItems[i] = readInt(); + } + return new AnnotationSetRefList(off, annotationSetRefItems); + } + + public AnnotationsDirectory readAnnotationsDirectory() { + int off = data.position(); + int classAnnotationsOffset = readInt(); + int fieldsSize = readInt(); + int methodsSize = readInt(); + int parameterListSize = readInt(); + + int[][] fieldAnnotations = new int[fieldsSize][2]; + for (int i = 0; i < fieldsSize; ++i) { + // field index + fieldAnnotations[i][0] = readInt(); + // annotations offset + fieldAnnotations[i][1] = readInt(); + } + + int[][] methodAnnotations = new int[methodsSize][2]; + for (int i = 0; i < methodsSize; ++i) { + // method index + methodAnnotations[i][0] = readInt(); + // annotation set offset + methodAnnotations[i][1] = readInt(); + } + + int[][] parameterAnnotations = new int[parameterListSize][2]; + for (int i = 0; i < parameterListSize; ++i) { + // method index + parameterAnnotations[i][0] = readInt(); + // annotations offset + parameterAnnotations[i][1] = readInt(); + } + + return new AnnotationsDirectory(off, classAnnotationsOffset, fieldAnnotations, methodAnnotations, parameterAnnotations); + } + + public EncodedValue readEncodedArray() { + int start = data.position(); + new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); + return new EncodedValue(start, getBytesFrom(start)); + } + + public void skip(int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + data.position(data.position() + count); + } + + public void skipWithAutoExpand(int count) { + ensureBufferSize(SizeOf.UBYTE * count); + skip(count); + } + + /** + * Skips bytes until the position is aligned to a multiple of 4. + */ + public void alignToFourBytes() { + data.position((data.position() + 3) & ~3); + } + + /** + * Writes 0x00 until the position is aligned to a multiple of 4. + */ + public void alignToFourBytesWithZeroFill() { + int alignedPos = SizeOf.roundToTimesOfFour(data.position()); + ensureBufferSize((alignedPos - data.position()) * SizeOf.UBYTE); + while ((data.position() & 3) != 0) { + data.put((byte) 0); + } + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + @Override + public void writeByte(int b) { + ensureBufferSize(SizeOf.UBYTE); + data.put((byte) b); + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + public void writeShort(short i) { + ensureBufferSize(SizeOf.USHORT); + data.putShort(i); + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + public void writeUnsignedShort(int i) { + short s = (short) i; + if (i != (s & 0xffff)) { + throw new IllegalArgumentException("Expected an unsigned short: " + i); + } + writeShort(s); + } + + public void writeInt(int i) { + ensureBufferSize(SizeOf.UINT); + this.data.putInt(i); + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + public void write(byte[] bytes) { + ensureBufferSize(bytes.length * SizeOf.UBYTE); + this.data.put(bytes); + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + public void write(short[] shorts) { + ensureBufferSize(shorts.length * SizeOf.USHORT); + for (short s : shorts) { + writeShort(s); + } + if (this.data.position() > this.dataBound) { + this.dataBound = this.data.position(); + } + } + + public void writeUleb128(int i) { + Leb128.writeUnsignedLeb128(this, i); + } + + public void writeUleb128p1(int i) { + writeUleb128(i + 1); + } + + public void writeSleb128(int i) { + Leb128.writeSignedLeb128(this, i); + } + + /** + * Write String data into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeStringData(StringData stringData) { + int off = data.position(); + try { + int length = stringData.value.length(); + writeUleb128(length); + write(Mutf8.encode(stringData.value)); + writeByte(0); + return off; + } catch (UTFDataFormatException e) { + throw new AssertionError(e); + } + } + + /** + * Write TypeList item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeTypeList(TypeList typeList) { + int off = data.position(); + short[] types = typeList.types; + writeInt(types.length); + for (short type : types) { + writeShort(type); + } + return off; + } + + /** + * Write FieldId item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeFieldId(FieldId fieldId) { + int off = data.position(); + writeUnsignedShort(fieldId.declaringClassIndex); + writeUnsignedShort(fieldId.typeIndex); + writeInt(fieldId.nameIndex); + return off; + } + + /** + * Write MethodId item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeMethodId(MethodId methodId) { + int off = data.position(); + writeUnsignedShort(methodId.declaringClassIndex); + writeUnsignedShort(methodId.protoIndex); + writeInt(methodId.nameIndex); + return off; + } + + /** + * Write ProtoId item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeProtoId(ProtoId protoId) { + int off = data.position(); + writeInt(protoId.shortyIndex); + writeInt(protoId.returnTypeIndex); + writeInt(protoId.parametersOffset); + return off; + } + + /** + * Write ClassDef item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeClassDef(ClassDef classDef) { + int off = data.position(); + writeInt(classDef.typeIndex); + writeInt(classDef.accessFlags); + writeInt(classDef.supertypeIndex); + writeInt(classDef.interfacesOffset); + writeInt(classDef.sourceFileIndex); + writeInt(classDef.annotationsOffset); + writeInt(classDef.classDataOffset); + writeInt(classDef.staticValuesOffset); + return off; + } + + /** + * Write Code item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeCode(Code code) { + int off = data.position(); + writeUnsignedShort(code.registersSize); + writeUnsignedShort(code.insSize); + writeUnsignedShort(code.outsSize); + writeUnsignedShort(code.tries.length); + writeInt(code.debugInfoOffset); + writeInt(code.instructions.length); + write(code.instructions); + + if (code.tries.length > 0) { + if ((code.instructions.length & 1) == 1) { + writeShort((short) 0); // padding + } + + /* + * We can't write the tries until we've written the catch handlers. + * Unfortunately they're in the opposite order in the dex file so we + * need to transform them out-of-order. + */ + int posBeforeTries = data.position(); + skipWithAutoExpand(code.tries.length * SizeOf.TRY_ITEM); + int[] offsets = writeCatchHandlers(code.catchHandlers); + int posAfterCatchHandlers = data.position(); + data.position(posBeforeTries); + writeTries(code.tries, offsets); + data.position(posAfterCatchHandlers); + } + return off; + } + + private int[] writeCatchHandlers(Code.CatchHandler[] catchHandlers) { + int baseOffset = data.position(); + writeUleb128(catchHandlers.length); + int[] offsets = new int[catchHandlers.length]; + for (int i = 0; i < catchHandlers.length; i++) { + offsets[i] = data.position() - baseOffset; + writeCatchHandler(catchHandlers[i]); + } + return offsets; + } + + private void writeCatchHandler(Code.CatchHandler catchHandler) { + int catchAllAddress = catchHandler.catchAllAddress; + int[] typeIndexes = catchHandler.typeIndexes; + int[] addresses = catchHandler.addresses; + + if (catchAllAddress != -1) { + writeSleb128(-typeIndexes.length); + } else { + writeSleb128(typeIndexes.length); + } + + for (int i = 0; i < typeIndexes.length; i++) { + writeUleb128(typeIndexes[i]); + writeUleb128(addresses[i]); + } + + if (catchAllAddress != -1) { + writeUleb128(catchAllAddress); + } + } + + private void writeTries(Code.Try[] tries, int[] catchHandlerOffsets) { + for (Code.Try tryItem : tries) { + writeInt(tryItem.startAddress); + writeUnsignedShort(tryItem.instructionCount); + writeUnsignedShort(catchHandlerOffsets[tryItem.catchHandlerIndex]); + } + } + + /** + * Write DebugInfo item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeDebugInfoItem(DebugInfoItem debugInfoItem) { + int off = data.position(); + + writeUleb128(debugInfoItem.lineStart); + + int parametersSize = debugInfoItem.parameterNames.length; + writeUleb128(parametersSize); + + for (int i = 0; i < parametersSize; ++i) { + int parameterName = debugInfoItem.parameterNames[i]; + writeUleb128p1(parameterName); + } + + write(debugInfoItem.infoSTM); + + return off; + } + + /** + * Write ClassData item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeClassData(ClassData classData) { + int off = data.position(); + writeUleb128(classData.staticFields.length); + writeUleb128(classData.instanceFields.length); + writeUleb128(classData.directMethods.length); + writeUleb128(classData.virtualMethods.length); + writeFields(classData.staticFields); + writeFields(classData.instanceFields); + writeMethods(classData.directMethods); + writeMethods(classData.virtualMethods); + return off; + } + + private void writeFields(ClassData.Field[] fields) { + int lastOutFieldIndex = 0; + for (ClassData.Field field : fields) { + writeUleb128(field.fieldIndex - lastOutFieldIndex); + lastOutFieldIndex = field.fieldIndex; + writeUleb128(field.accessFlags); + } + } + + private void writeMethods(ClassData.Method[] methods) { + int lastOutMethodIndex = 0; + for (ClassData.Method method : methods) { + writeUleb128(method.methodIndex - lastOutMethodIndex); + lastOutMethodIndex = method.methodIndex; + writeUleb128(method.accessFlags); + writeUleb128(method.codeOffset); + } + } + + /** + * Write Annotation item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeAnnotation(Annotation annotation) { + int off = data.position(); + writeByte(annotation.visibility); + writeEncodedArray(annotation.encodedAnnotation); + return off; + } + + /** + * Write AnnotationSet item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeAnnotationSet(AnnotationSet annotationSet) { + int off = data.position(); + writeInt(annotationSet.annotationOffsets.length); + for (int annotationOffset : annotationSet.annotationOffsets) { + writeInt(annotationOffset); + } + return off; + } + + /** + * Write AnnotationSetRefList item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeAnnotationSetRefList(AnnotationSetRefList annotationSetRefList) { + int off = data.position(); + writeInt(annotationSetRefList.annotationSetRefItems.length); + for (int annotationSetRefItem : annotationSetRefList.annotationSetRefItems) { + writeInt(annotationSetRefItem); + } + return off; + } + + /** + * Write AnnotationDirectory item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeAnnotationsDirectory(AnnotationsDirectory annotationsDirectory) { + int off = data.position(); + writeInt(annotationsDirectory.classAnnotationsOffset); + writeInt(annotationsDirectory.fieldAnnotations.length); + writeInt(annotationsDirectory.methodAnnotations.length); + writeInt(annotationsDirectory.parameterAnnotations.length); + + for (int[] fieldAnnotation : annotationsDirectory.fieldAnnotations) { + writeInt(fieldAnnotation[0]); + writeInt(fieldAnnotation[1]); + } + + for (int[] methodAnnotation : annotationsDirectory.methodAnnotations) { + writeInt(methodAnnotation[0]); + writeInt(methodAnnotation[1]); + } + + for (int[] parameterAnnotation : annotationsDirectory.parameterAnnotations) { + writeInt(parameterAnnotation[0]); + writeInt(parameterAnnotation[1]); + } + return off; + } + + /** + * Write EncodedValue/EncodedArray item into current section. + * + * @return real offset of item we've just written in this section. + */ + public int writeEncodedArray(EncodedValue encodedValue) { + int off = data.position(); + write(encodedValue.data); + return off; + } +} \ No newline at end of file diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteInput.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteInput.java new file mode 100644 index 00000000..5c35c879 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteInput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex.util; + +/** + * A byte source. + */ +public interface ByteInput { + + /** + * Returns a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been read. + */ + byte readByte(); +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteOutput.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteOutput.java new file mode 100644 index 00000000..6cc20f5e --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteOutput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex.util; + +/** + * A byte sink. + */ +public interface ByteOutput { + + /** + * Writes a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been written. + */ + void writeByte(int i); +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/CompareUtils.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/CompareUtils.java new file mode 100644 index 00000000..7290f1f3 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/CompareUtils.java @@ -0,0 +1,253 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex.util; + +import java.util.Comparator; + +/** + * *** This file is NOT a part of AOSP. *** + * Created by tangyinsheng on 2016/6/28. + */ +public final class CompareUtils { + private CompareUtils() { } + + public static int uCompare(byte ubyteA, byte ubyteB) { + if (ubyteA == ubyteB) { + return 0; + } + int a = ubyteA & 0xFF; + int b = ubyteB & 0xFF; + return a < b ? -1 : 1; + } + + public static int uCompare(short ushortA, short ushortB) { + if (ushortA == ushortB) { + return 0; + } + int a = ushortA & 0xFFFF; + int b = ushortB & 0xFFFF; + return a < b ? -1 : 1; + } + + public static int uCompare(int uintA, int uintB) { + if (uintA == uintB) { + return 0; + } + long a = uintA & 0xFFFFFFFFL; + long b = uintB & 0xFFFFFFFFL; + return a < b ? -1 : 1; + } + + public static int uArrCompare(byte[] ubyteArrA, byte[] ubyteArrB) { + int lenA = ubyteArrA.length; + int lenB = ubyteArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = uCompare(ubyteArrA[i], ubyteArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int uArrCompare(short[] ushortArrA, short[] ushortArrB) { + int lenA = ushortArrA.length; + int lenB = ushortArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = uCompare(ushortArrA[i], ushortArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int uArrCompare(int[] uintArrA, int[] uintArrB) { + int lenA = uintArrA.length; + int lenB = uintArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = uCompare(uintArrA[i], uintArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int sCompare(byte sbyteA, byte sbyteB) { + if (sbyteA == sbyteB) { + return 0; + } + return sbyteA < sbyteB ? -1 : 1; + } + + public static int sCompare(short sshortA, short sshortB) { + if (sshortA == sshortB) { + return 0; + } + return sshortA < sshortB ? -1 : 1; + } + + public static int sCompare(int sintA, int sintB) { + if (sintA == sintB) { + return 0; + } + return sintA < sintB ? -1 : 1; + } + + public static int sCompare(long slongA, long slongB) { + if (slongA == slongB) { + return 0; + } + return slongA < slongB ? -1 : 1; + } + + public static int sArrCompare(byte[] sbyteArrA, byte[] sbyteArrB) { + int lenA = sbyteArrA.length; + int lenB = sbyteArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = sCompare(sbyteArrA[i], sbyteArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int sArrCompare(short[] sshortArrA, short[] sshortArrB) { + int lenA = sshortArrA.length; + int lenB = sshortArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = sCompare(sshortArrA[i], sshortArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int sArrCompare(int[] sintArrA, int[] sintArrB) { + int lenA = sintArrA.length; + int lenB = sintArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = sCompare(sintArrA[i], sintArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int sArrCompare(long[] slongArrA, long[] slongArrB) { + int lenA = slongArrA.length; + int lenB = slongArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = sCompare(slongArrA[i], slongArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static > int aArrCompare(T[] aArrA, T[] aArrB) { + int lenA = aArrA.length; + int lenB = aArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = aArrA[i].compareTo(aArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } + + public static int aArrCompare(T[] aArrA, T[] aArrB, Comparator cmptor) { + int lenA = aArrA.length; + int lenB = aArrB.length; + if (lenA < lenB) { + return -1; + } else + if (lenA > lenB) { + return 1; + } else { + for (int i = 0; i < lenA; ++i) { + int res = cmptor.compare(aArrA[i], aArrB[i]); + if (res != 0) { + return res; + } + } + return 0; + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/FileUtils.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/FileUtils.java new file mode 100644 index 00000000..1130eb86 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/FileUtils.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dex.util; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * *** This file is NOT a part of AOSP. *** + * File I/O utilities. + */ +public final class FileUtils { + private FileUtils() { + } + + /** + * Reads the named file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param fileName {@code non-null;} name of the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(String fileName) throws IOException { + File file = new File(fileName); + return readFile(file); + } + + /** + * Reads the given file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param file {@code non-null;} the file to read + * @return {@code non-null;} contents of the file + * @throws IOException + */ + public static byte[] readFile(File file) throws IOException { + if (!file.exists()) { + throw new RuntimeException(file + ": file not found"); + } + + if (!file.isFile()) { + throw new RuntimeException(file + ": not a file"); + } + + if (!file.canRead()) { + throw new RuntimeException(file + ": file not readable"); + } + + long longLength = file.length(); + int length = (int) longLength; + if (length != longLength) { + throw new RuntimeException(file + ": file too long"); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(length); + + InputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[8192]; + int bytesRead = 0; + while ((bytesRead = in.read(buffer)) > 0) { + baos.write(buffer, 0, bytesRead); + } + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception e) { + // ignored. + } + } + } + + return baos.toByteArray(); + } + + public static byte[] readStream(InputStream is) throws IOException { + return readStream(is, 32 * 1024); + } + + public static byte[] readStream(InputStream is, int initSize) throws IOException { + if (initSize <= 0) { + initSize = 32 * 1024; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(initSize); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = is.read(buffer)) > 0) { + baos.write(buffer, 0, bytesRead); + } + return baos.toByteArray(); + } + + /** + * Returns true if {@code fileName} names a .zip, .jar, or .apk. + */ + public static boolean hasArchiveSuffix(String fileName) { + return fileName.endsWith(".zip") + || fileName.endsWith(".jar") + || fileName.endsWith(".apk"); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/CodeCursor.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/CodeCursor.java new file mode 100644 index 00000000..8514b6b7 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/CodeCursor.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.utils.SparseIntArray; + +/** + * Cursor over code units, for reading or writing out Dalvik bytecode. + */ +public abstract class CodeCursor { + /** base address map */ + private final SparseIntArray baseAddressMap; + + /** next index within {@link #array} to read from or write to */ + private int cursor; + + /** + * Constructs an instance. + */ + public CodeCursor() { + this.baseAddressMap = new SparseIntArray(); + this.cursor = 0; + } + + /** + * Gets the cursor. The cursor is the offset in code units from + * the start of the input of the next code unit to be read or + * written, where the input generally consists of the code for a + * single method. + */ + public final int cursor() { + return cursor; + } + + /** + * Gets the base address associated with the current cursor. This + * differs from the cursor value when explicitly set (by {@link + * #setBaseAddress}). This is used, in particular, to convey base + * addresses to switch data payload instructions, whose relative + * addresses are relative to the address of a dependant switch + * instruction. + */ + public final int baseAddressForCursor() { + int index = baseAddressMap.indexOfKey(cursor); + if (index < 0) { + return cursor; + } else { + return baseAddressMap.valueAt(index); + } + } + + /** + * Sets the base address for the given target address to be as indicated. + * + * @see #baseAddressForCursor + */ + public final void setBaseAddress(int targetAddress, int baseAddress) { + baseAddressMap.put(targetAddress, baseAddress); + } + + /** + * Reset this cursor's status. + */ + public void reset() { + this.baseAddressMap.clear(); + this.cursor = 0; + } + + /** + * Advance the cursor by the indicated amount. + */ + protected final void advance(int amount) { + cursor += amount; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionCodec.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionCodec.java new file mode 100644 index 00000000..c0234f24 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionCodec.java @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dx.util.Hex; + +/** + * Encode/Decode instruction opcode. + */ +public final class InstructionCodec { + /** "Unknown." Used for undefined opcodes. */ + public static final int INDEX_TYPE_UNKNOWN = 0; + /** no index used */ + public static final int INDEX_TYPE_NONE = 1; + /** type reference index */ + public static final int INDEX_TYPE_TYPE_REF = 2; + /** string reference index */ + public static final int INDEX_TYPE_STRING_REF = 3; + /** method reference index */ + public static final int INDEX_TYPE_METHOD_REF = 4; + /** field reference index */ + public static final int INDEX_TYPE_FIELD_REF = 5; + /** "Unknown." Used for undefined opcodes. */ + public static final int INSN_FORMAT_UNKNOWN = 0; + public static final int INSN_FORMAT_00X = 1; + public static final int INSN_FORMAT_10T = 2; + public static final int INSN_FORMAT_10X = 3; + public static final int INSN_FORMAT_11N = 4; + public static final int INSN_FORMAT_11X = 5; + public static final int INSN_FORMAT_12X = 6; + public static final int INSN_FORMAT_20T = 7; + public static final int INSN_FORMAT_21C = 8; + public static final int INSN_FORMAT_21H = 9; + public static final int INSN_FORMAT_21S = 10; + public static final int INSN_FORMAT_21T = 11; + public static final int INSN_FORMAT_22B = 12; + public static final int INSN_FORMAT_22C = 13; + public static final int INSN_FORMAT_22S = 14; + public static final int INSN_FORMAT_22T = 15; + public static final int INSN_FORMAT_22X = 16; + public static final int INSN_FORMAT_23X = 17; + public static final int INSN_FORMAT_30T = 18; + public static final int INSN_FORMAT_31C = 19; + public static final int INSN_FORMAT_31I = 20; + public static final int INSN_FORMAT_31T = 21; + public static final int INSN_FORMAT_32X = 22; + public static final int INSN_FORMAT_35C = 23; + public static final int INSN_FORMAT_3RC = 24; + public static final int INSN_FORMAT_51L = 25; + public static final int INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD = 26; + public static final int INSN_FORMAT_PACKED_SWITCH_PAYLOAD = 27; + public static final int INSN_FORMAT_SPARSE_SWITCH_PAYLOAD = 28; + private InstructionCodec() { + throw new UnsupportedOperationException(); + } + + public static short codeUnit(int lowByte, int highByte) { + if ((lowByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus lowByte"); + } + + if ((highByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus highByte"); + } + + return (short) (lowByte | (highByte << 8)); + } + + public static short codeUnit(int nibble0, int nibble1, int nibble2, + int nibble3) { + if ((nibble0 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble0"); + } + + if ((nibble1 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble1"); + } + + if ((nibble2 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble2"); + } + + if ((nibble3 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble3"); + } + + return (short) (nibble0 | (nibble1 << 4) + | (nibble2 << 8) | (nibble3 << 12)); + } + + public static int makeByte(int lowNibble, int highNibble) { + if ((lowNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus lowNibble"); + } + + if ((highNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus highNibble"); + } + + return lowNibble | (highNibble << 4); + } + + public static short asUnsignedUnit(int value) { + if ((value & ~0xffff) != 0) { + throw new IllegalArgumentException("bogus unsigned code unit"); + } + + return (short) value; + } + + public static short unit0(int value) { + return (short) value; + } + + public static short unit1(int value) { + return (short) (value >> 16); + } + + public static short unit0(long value) { + return (short) value; + } + + public static short unit1(long value) { + return (short) (value >> 16); + } + + public static short unit2(long value) { + return (short) (value >> 32); + } + + public static short unit3(long value) { + return (short) (value >> 48); + } + + public static int byte0(int value) { + return value & 0xff; + } + + public static int byte1(int value) { + return (value >> 8) & 0xff; + } + + public static int nibble0(int value) { + return value & 0xf; + } + + public static int nibble1(int value) { + return (value >> 4) & 0xf; + } + + public static int nibble2(int value) { + return (value >> 8) & 0xf; + } + + public static int nibble3(int value) { + return (value >> 12) & 0xf; + } + + public static int getTargetByte(int target, int baseAddress) { + int relativeTarget = getTarget(target, baseAddress); + + if (relativeTarget != (byte) relativeTarget) { + throw new DexException( + "Target out of range: " + + Hex.s4(relativeTarget) + + ", perhaps you need to enable force jumbo mode." + ); + } + + return relativeTarget & 0xff; + } + + public static short getTargetUnit(int target, int baseAddress) { + int relativeTarget = getTarget(target, baseAddress); + + if (relativeTarget != (short) relativeTarget) { + throw new DexException( + "Target out of range: " + + Hex.s4(relativeTarget) + + ", perhaps you need to enable force jumbo mode." + ); + } + + return (short) relativeTarget; + } + + public static int getTarget(int target, int baseAddress) { + return target - baseAddress; + } + + public static int getLiteralByte(long literal) { + if (literal != (byte) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xff; + } + + public static short getLiteralUnit(long literal) { + if (literal != (short) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (short) literal; + } + + public static int getLiteralInt(long literal) { + if (literal != (int) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal; + } + + public static int getLiteralNibble(long literal) { + if ((literal < -8) || (literal > 7)) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xf; + } + + /** + * Gets the A register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public static short getAUnit(int a) { + if ((a & ~0xffff) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the B register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public static short getBUnit(int b) { + if ((b & ~0xffff) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + public static int getInstructionIndexType(int opcode) { + switch (opcode) { + case Opcodes.CONST_STRING: + case Opcodes.CONST_STRING_JUMBO: { + return INDEX_TYPE_STRING_REF; + } + case Opcodes.CONST_CLASS: + case Opcodes.CHECK_CAST: + case Opcodes.INSTANCE_OF: + case Opcodes.NEW_INSTANCE: + case Opcodes.NEW_ARRAY: + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.FILLED_NEW_ARRAY_RANGE: { + return INDEX_TYPE_TYPE_REF; + } + case Opcodes.IGET: + case Opcodes.IGET_WIDE: + case Opcodes.IGET_OBJECT: + case Opcodes.IGET_BOOLEAN: + case Opcodes.IGET_BYTE: + case Opcodes.IGET_CHAR: + case Opcodes.IGET_SHORT: + case Opcodes.IPUT: + case Opcodes.IPUT_WIDE: + case Opcodes.IPUT_OBJECT: + case Opcodes.IPUT_BOOLEAN: + case Opcodes.IPUT_BYTE: + case Opcodes.IPUT_CHAR: + case Opcodes.IPUT_SHORT: + case Opcodes.SGET: + case Opcodes.SGET_WIDE: + case Opcodes.SGET_OBJECT: + case Opcodes.SGET_BOOLEAN: + case Opcodes.SGET_BYTE: + case Opcodes.SGET_CHAR: + case Opcodes.SGET_SHORT: + case Opcodes.SPUT: + case Opcodes.SPUT_WIDE: + case Opcodes.SPUT_OBJECT: + case Opcodes.SPUT_BOOLEAN: + case Opcodes.SPUT_BYTE: + case Opcodes.SPUT_CHAR: + case Opcodes.SPUT_SHORT: { + return INDEX_TYPE_FIELD_REF; + } + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: + case Opcodes.INVOKE_VIRTUAL_RANGE: + case Opcodes.INVOKE_SUPER_RANGE: + case Opcodes.INVOKE_DIRECT_RANGE: + case Opcodes.INVOKE_STATIC_RANGE: + case Opcodes.INVOKE_INTERFACE_RANGE: { + return INDEX_TYPE_METHOD_REF; + } + case Opcodes.SPECIAL_FORMAT: + case Opcodes.PACKED_SWITCH_PAYLOAD: + case Opcodes.SPARSE_SWITCH_PAYLOAD: + case Opcodes.FILL_ARRAY_DATA_PAYLOAD: + case Opcodes.NOP: + case Opcodes.MOVE: + case Opcodes.MOVE_FROM16: + case Opcodes.MOVE_16: + case Opcodes.MOVE_WIDE: + case Opcodes.MOVE_WIDE_FROM16: + case Opcodes.MOVE_WIDE_16: + case Opcodes.MOVE_OBJECT: + case Opcodes.MOVE_OBJECT_FROM16: + case Opcodes.MOVE_OBJECT_16: + case Opcodes.MOVE_RESULT: + case Opcodes.MOVE_RESULT_WIDE: + case Opcodes.MOVE_RESULT_OBJECT: + case Opcodes.MOVE_EXCEPTION: + case Opcodes.RETURN_VOID: + case Opcodes.RETURN: + case Opcodes.RETURN_WIDE: + case Opcodes.RETURN_OBJECT: + case Opcodes.CONST_4: + case Opcodes.CONST_16: + case Opcodes.CONST: + case Opcodes.CONST_HIGH16: + case Opcodes.CONST_WIDE_16: + case Opcodes.CONST_WIDE_32: + case Opcodes.CONST_WIDE: + case Opcodes.CONST_WIDE_HIGH16: + case Opcodes.MONITOR_ENTER: + case Opcodes.MONITOR_EXIT: + case Opcodes.ARRAY_LENGTH: + case Opcodes.FILL_ARRAY_DATA: + case Opcodes.THROW: + case Opcodes.GOTO: + case Opcodes.GOTO_16: + case Opcodes.GOTO_32: + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: + case Opcodes.CMPL_FLOAT: + case Opcodes.CMPG_FLOAT: + case Opcodes.CMPL_DOUBLE: + case Opcodes.CMPG_DOUBLE: + case Opcodes.CMP_LONG: + case Opcodes.IF_EQ: + case Opcodes.IF_NE: + case Opcodes.IF_LT: + case Opcodes.IF_GE: + case Opcodes.IF_GT: + case Opcodes.IF_LE: + case Opcodes.IF_EQZ: + case Opcodes.IF_NEZ: + case Opcodes.IF_LTZ: + case Opcodes.IF_GEZ: + case Opcodes.IF_GTZ: + case Opcodes.IF_LEZ: + case Opcodes.AGET: + case Opcodes.AGET_WIDE: + case Opcodes.AGET_OBJECT: + case Opcodes.AGET_BOOLEAN: + case Opcodes.AGET_BYTE: + case Opcodes.AGET_CHAR: + case Opcodes.AGET_SHORT: + case Opcodes.APUT: + case Opcodes.APUT_WIDE: + case Opcodes.APUT_OBJECT: + case Opcodes.APUT_BOOLEAN: + case Opcodes.APUT_BYTE: + case Opcodes.APUT_CHAR: + case Opcodes.APUT_SHORT: + case Opcodes.NEG_INT: + case Opcodes.NOT_INT: + case Opcodes.NEG_LONG: + case Opcodes.NOT_LONG: + case Opcodes.NEG_FLOAT: + case Opcodes.NEG_DOUBLE: + case Opcodes.INT_TO_LONG: + case Opcodes.INT_TO_FLOAT: + case Opcodes.INT_TO_DOUBLE: + case Opcodes.LONG_TO_INT: + case Opcodes.LONG_TO_FLOAT: + case Opcodes.LONG_TO_DOUBLE: + case Opcodes.FLOAT_TO_INT: + case Opcodes.FLOAT_TO_LONG: + case Opcodes.FLOAT_TO_DOUBLE: + case Opcodes.DOUBLE_TO_INT: + case Opcodes.DOUBLE_TO_LONG: + case Opcodes.DOUBLE_TO_FLOAT: + case Opcodes.INT_TO_BYTE: + case Opcodes.INT_TO_CHAR: + case Opcodes.INT_TO_SHORT: + case Opcodes.ADD_INT: + case Opcodes.SUB_INT: + case Opcodes.MUL_INT: + case Opcodes.DIV_INT: + case Opcodes.REM_INT: + case Opcodes.AND_INT: + case Opcodes.OR_INT: + case Opcodes.XOR_INT: + case Opcodes.SHL_INT: + case Opcodes.SHR_INT: + case Opcodes.USHR_INT: + case Opcodes.ADD_LONG: + case Opcodes.SUB_LONG: + case Opcodes.MUL_LONG: + case Opcodes.DIV_LONG: + case Opcodes.REM_LONG: + case Opcodes.AND_LONG: + case Opcodes.OR_LONG: + case Opcodes.XOR_LONG: + case Opcodes.SHL_LONG: + case Opcodes.SHR_LONG: + case Opcodes.USHR_LONG: + case Opcodes.ADD_FLOAT: + case Opcodes.SUB_FLOAT: + case Opcodes.MUL_FLOAT: + case Opcodes.DIV_FLOAT: + case Opcodes.REM_FLOAT: + case Opcodes.ADD_DOUBLE: + case Opcodes.SUB_DOUBLE: + case Opcodes.MUL_DOUBLE: + case Opcodes.DIV_DOUBLE: + case Opcodes.REM_DOUBLE: + case Opcodes.ADD_INT_2ADDR: + case Opcodes.SUB_INT_2ADDR: + case Opcodes.MUL_INT_2ADDR: + case Opcodes.DIV_INT_2ADDR: + case Opcodes.REM_INT_2ADDR: + case Opcodes.AND_INT_2ADDR: + case Opcodes.OR_INT_2ADDR: + case Opcodes.XOR_INT_2ADDR: + case Opcodes.SHL_INT_2ADDR: + case Opcodes.SHR_INT_2ADDR: + case Opcodes.USHR_INT_2ADDR: + case Opcodes.ADD_LONG_2ADDR: + case Opcodes.SUB_LONG_2ADDR: + case Opcodes.MUL_LONG_2ADDR: + case Opcodes.DIV_LONG_2ADDR: + case Opcodes.REM_LONG_2ADDR: + case Opcodes.AND_LONG_2ADDR: + case Opcodes.OR_LONG_2ADDR: + case Opcodes.XOR_LONG_2ADDR: + case Opcodes.SHL_LONG_2ADDR: + case Opcodes.SHR_LONG_2ADDR: + case Opcodes.USHR_LONG_2ADDR: + case Opcodes.ADD_FLOAT_2ADDR: + case Opcodes.SUB_FLOAT_2ADDR: + case Opcodes.MUL_FLOAT_2ADDR: + case Opcodes.DIV_FLOAT_2ADDR: + case Opcodes.REM_FLOAT_2ADDR: + case Opcodes.ADD_DOUBLE_2ADDR: + case Opcodes.SUB_DOUBLE_2ADDR: + case Opcodes.MUL_DOUBLE_2ADDR: + case Opcodes.DIV_DOUBLE_2ADDR: + case Opcodes.REM_DOUBLE_2ADDR: + case Opcodes.ADD_INT_LIT16: + case Opcodes.RSUB_INT: + case Opcodes.MUL_INT_LIT16: + case Opcodes.DIV_INT_LIT16: + case Opcodes.REM_INT_LIT16: + case Opcodes.AND_INT_LIT16: + case Opcodes.OR_INT_LIT16: + case Opcodes.XOR_INT_LIT16: + case Opcodes.ADD_INT_LIT8: + case Opcodes.RSUB_INT_LIT8: + case Opcodes.MUL_INT_LIT8: + case Opcodes.DIV_INT_LIT8: + case Opcodes.REM_INT_LIT8: + case Opcodes.AND_INT_LIT8: + case Opcodes.OR_INT_LIT8: + case Opcodes.XOR_INT_LIT8: + case Opcodes.SHL_INT_LIT8: + case Opcodes.SHR_INT_LIT8: + case Opcodes.USHR_INT_LIT8: { + return INDEX_TYPE_NONE; + } + default: { + return INDEX_TYPE_UNKNOWN; + } + } + } + + public static int getInstructionFormat(int opcode) { + switch (opcode) { + case Opcodes.SPECIAL_FORMAT: { + return INSN_FORMAT_00X; + } + case Opcodes.GOTO: { + return INSN_FORMAT_10T; + } + case Opcodes.NOP: + case Opcodes.RETURN_VOID: { + return INSN_FORMAT_10X; + } + case Opcodes.CONST_4: { + return INSN_FORMAT_11N; + } + case Opcodes.MOVE_RESULT: + case Opcodes.MOVE_RESULT_WIDE: + case Opcodes.MOVE_RESULT_OBJECT: + case Opcodes.MOVE_EXCEPTION: + case Opcodes.RETURN: + case Opcodes.RETURN_WIDE: + case Opcodes.RETURN_OBJECT: + case Opcodes.MONITOR_ENTER: + case Opcodes.MONITOR_EXIT: + case Opcodes.THROW: { + return INSN_FORMAT_11X; + } + case Opcodes.MOVE: + case Opcodes.MOVE_WIDE: + case Opcodes.MOVE_OBJECT: + case Opcodes.ARRAY_LENGTH: + case Opcodes.NEG_INT: + case Opcodes.NOT_INT: + case Opcodes.NEG_LONG: + case Opcodes.NOT_LONG: + case Opcodes.NEG_FLOAT: + case Opcodes.NEG_DOUBLE: + case Opcodes.INT_TO_LONG: + case Opcodes.INT_TO_FLOAT: + case Opcodes.INT_TO_DOUBLE: + case Opcodes.LONG_TO_INT: + case Opcodes.LONG_TO_FLOAT: + case Opcodes.LONG_TO_DOUBLE: + case Opcodes.FLOAT_TO_INT: + case Opcodes.FLOAT_TO_LONG: + case Opcodes.FLOAT_TO_DOUBLE: + case Opcodes.DOUBLE_TO_INT: + case Opcodes.DOUBLE_TO_LONG: + case Opcodes.DOUBLE_TO_FLOAT: + case Opcodes.INT_TO_BYTE: + case Opcodes.INT_TO_CHAR: + case Opcodes.INT_TO_SHORT: + case Opcodes.ADD_INT_2ADDR: + case Opcodes.SUB_INT_2ADDR: + case Opcodes.MUL_INT_2ADDR: + case Opcodes.DIV_INT_2ADDR: + case Opcodes.REM_INT_2ADDR: + case Opcodes.AND_INT_2ADDR: + case Opcodes.OR_INT_2ADDR: + case Opcodes.XOR_INT_2ADDR: + case Opcodes.SHL_INT_2ADDR: + case Opcodes.SHR_INT_2ADDR: + case Opcodes.USHR_INT_2ADDR: + case Opcodes.ADD_LONG_2ADDR: + case Opcodes.SUB_LONG_2ADDR: + case Opcodes.MUL_LONG_2ADDR: + case Opcodes.DIV_LONG_2ADDR: + case Opcodes.REM_LONG_2ADDR: + case Opcodes.AND_LONG_2ADDR: + case Opcodes.OR_LONG_2ADDR: + case Opcodes.XOR_LONG_2ADDR: + case Opcodes.SHL_LONG_2ADDR: + case Opcodes.SHR_LONG_2ADDR: + case Opcodes.USHR_LONG_2ADDR: + case Opcodes.ADD_FLOAT_2ADDR: + case Opcodes.SUB_FLOAT_2ADDR: + case Opcodes.MUL_FLOAT_2ADDR: + case Opcodes.DIV_FLOAT_2ADDR: + case Opcodes.REM_FLOAT_2ADDR: + case Opcodes.ADD_DOUBLE_2ADDR: + case Opcodes.SUB_DOUBLE_2ADDR: + case Opcodes.MUL_DOUBLE_2ADDR: + case Opcodes.DIV_DOUBLE_2ADDR: + case Opcodes.REM_DOUBLE_2ADDR: { + return INSN_FORMAT_12X; + } + case Opcodes.GOTO_16: { + return INSN_FORMAT_20T; + } + case Opcodes.CONST_STRING: + case Opcodes.CONST_CLASS: + case Opcodes.CHECK_CAST: + case Opcodes.NEW_INSTANCE: + case Opcodes.SGET: + case Opcodes.SGET_WIDE: + case Opcodes.SGET_OBJECT: + case Opcodes.SGET_BOOLEAN: + case Opcodes.SGET_BYTE: + case Opcodes.SGET_CHAR: + case Opcodes.SGET_SHORT: + case Opcodes.SPUT: + case Opcodes.SPUT_WIDE: + case Opcodes.SPUT_OBJECT: + case Opcodes.SPUT_BOOLEAN: + case Opcodes.SPUT_BYTE: + case Opcodes.SPUT_CHAR: + case Opcodes.SPUT_SHORT: { + return INSN_FORMAT_21C; + } + case Opcodes.CONST_HIGH16: + case Opcodes.CONST_WIDE_HIGH16: { + return INSN_FORMAT_21H; + } + case Opcodes.CONST_16: + case Opcodes.CONST_WIDE_16: { + return INSN_FORMAT_21S; + } + case Opcodes.IF_EQZ: + case Opcodes.IF_NEZ: + case Opcodes.IF_LTZ: + case Opcodes.IF_GEZ: + case Opcodes.IF_GTZ: + case Opcodes.IF_LEZ: { + return INSN_FORMAT_21T; + } + case Opcodes.ADD_INT_LIT8: + case Opcodes.RSUB_INT_LIT8: + case Opcodes.MUL_INT_LIT8: + case Opcodes.DIV_INT_LIT8: + case Opcodes.REM_INT_LIT8: + case Opcodes.AND_INT_LIT8: + case Opcodes.OR_INT_LIT8: + case Opcodes.XOR_INT_LIT8: + case Opcodes.SHL_INT_LIT8: + case Opcodes.SHR_INT_LIT8: + case Opcodes.USHR_INT_LIT8: { + return INSN_FORMAT_22B; + } + case Opcodes.INSTANCE_OF: + case Opcodes.NEW_ARRAY: + case Opcodes.IGET: + case Opcodes.IGET_WIDE: + case Opcodes.IGET_OBJECT: + case Opcodes.IGET_BOOLEAN: + case Opcodes.IGET_BYTE: + case Opcodes.IGET_CHAR: + case Opcodes.IGET_SHORT: + case Opcodes.IPUT: + case Opcodes.IPUT_WIDE: + case Opcodes.IPUT_OBJECT: + case Opcodes.IPUT_BOOLEAN: + case Opcodes.IPUT_BYTE: + case Opcodes.IPUT_CHAR: + case Opcodes.IPUT_SHORT: { + return INSN_FORMAT_22C; + } + case Opcodes.ADD_INT_LIT16: + case Opcodes.RSUB_INT: + case Opcodes.MUL_INT_LIT16: + case Opcodes.DIV_INT_LIT16: + case Opcodes.REM_INT_LIT16: + case Opcodes.AND_INT_LIT16: + case Opcodes.OR_INT_LIT16: + case Opcodes.XOR_INT_LIT16: { + return INSN_FORMAT_22S; + } + case Opcodes.IF_EQ: + case Opcodes.IF_NE: + case Opcodes.IF_LT: + case Opcodes.IF_GE: + case Opcodes.IF_GT: + case Opcodes.IF_LE: { + return INSN_FORMAT_22T; + } + case Opcodes.MOVE_FROM16: + case Opcodes.MOVE_WIDE_FROM16: + case Opcodes.MOVE_OBJECT_FROM16: { + return INSN_FORMAT_22X; + } + case Opcodes.CMPL_FLOAT: + case Opcodes.CMPG_FLOAT: + case Opcodes.CMPL_DOUBLE: + case Opcodes.CMPG_DOUBLE: + case Opcodes.CMP_LONG: + case Opcodes.AGET: + case Opcodes.AGET_WIDE: + case Opcodes.AGET_OBJECT: + case Opcodes.AGET_BOOLEAN: + case Opcodes.AGET_BYTE: + case Opcodes.AGET_CHAR: + case Opcodes.AGET_SHORT: + case Opcodes.APUT: + case Opcodes.APUT_WIDE: + case Opcodes.APUT_OBJECT: + case Opcodes.APUT_BOOLEAN: + case Opcodes.APUT_BYTE: + case Opcodes.APUT_CHAR: + case Opcodes.APUT_SHORT: + case Opcodes.ADD_INT: + case Opcodes.SUB_INT: + case Opcodes.MUL_INT: + case Opcodes.DIV_INT: + case Opcodes.REM_INT: + case Opcodes.AND_INT: + case Opcodes.OR_INT: + case Opcodes.XOR_INT: + case Opcodes.SHL_INT: + case Opcodes.SHR_INT: + case Opcodes.USHR_INT: + case Opcodes.ADD_LONG: + case Opcodes.SUB_LONG: + case Opcodes.MUL_LONG: + case Opcodes.DIV_LONG: + case Opcodes.REM_LONG: + case Opcodes.AND_LONG: + case Opcodes.OR_LONG: + case Opcodes.XOR_LONG: + case Opcodes.SHL_LONG: + case Opcodes.SHR_LONG: + case Opcodes.USHR_LONG: + case Opcodes.ADD_FLOAT: + case Opcodes.SUB_FLOAT: + case Opcodes.MUL_FLOAT: + case Opcodes.DIV_FLOAT: + case Opcodes.REM_FLOAT: + case Opcodes.ADD_DOUBLE: + case Opcodes.SUB_DOUBLE: + case Opcodes.MUL_DOUBLE: + case Opcodes.DIV_DOUBLE: + case Opcodes.REM_DOUBLE: { + return INSN_FORMAT_23X; + } + case Opcodes.GOTO_32: { + return INSN_FORMAT_30T; + } + case Opcodes.CONST_STRING_JUMBO: { + return INSN_FORMAT_31C; + } + case Opcodes.CONST: + case Opcodes.CONST_WIDE_32: { + return INSN_FORMAT_31I; + } + case Opcodes.FILL_ARRAY_DATA: + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + return INSN_FORMAT_31T; + } + case Opcodes.MOVE_16: + case Opcodes.MOVE_WIDE_16: + case Opcodes.MOVE_OBJECT_16: { + return INSN_FORMAT_32X; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + return INSN_FORMAT_35C; + } + case Opcodes.FILLED_NEW_ARRAY_RANGE: + case Opcodes.INVOKE_VIRTUAL_RANGE: + case Opcodes.INVOKE_SUPER_RANGE: + case Opcodes.INVOKE_DIRECT_RANGE: + case Opcodes.INVOKE_STATIC_RANGE: + case Opcodes.INVOKE_INTERFACE_RANGE: { + return INSN_FORMAT_3RC; + } + case Opcodes.CONST_WIDE: { + return INSN_FORMAT_51L; + } + case Opcodes.PACKED_SWITCH_PAYLOAD: { + return INSN_FORMAT_PACKED_SWITCH_PAYLOAD; + } + case Opcodes.SPARSE_SWITCH_PAYLOAD: { + return INSN_FORMAT_SPARSE_SWITCH_PAYLOAD; + } + case Opcodes.FILL_ARRAY_DATA_PAYLOAD: { + return INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD; + } + default: { + return INSN_FORMAT_UNKNOWN; + } + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionComparator.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionComparator.java new file mode 100644 index 00000000..b90bac22 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionComparator.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dex.util.CompareUtils; +import com.tencent.tinker.android.dx.util.Hex; + +import java.io.EOFException; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Created by tangyinsheng on 2016/7/12. + */ +public abstract class InstructionComparator { + private final InstructionHolder[] insnHolders1; + private final InstructionHolder[] insnHolders2; + + public InstructionComparator(short[] insns1, short[] insns2) { + if (insns1 != null) { + ShortArrayCodeInput codeIn1 = new ShortArrayCodeInput(insns1); + this.insnHolders1 = readInstructionsIntoHolders(codeIn1, insns1.length); + } else { + this.insnHolders1 = null; + } + if (insns2 != null) { + ShortArrayCodeInput codeIn2 = new ShortArrayCodeInput(insns2); + this.insnHolders2 = readInstructionsIntoHolders(codeIn2, insns2.length); + } else { + this.insnHolders2 = null; + } + } + + private InstructionHolder[] readInstructionsIntoHolders(ShortArrayCodeInput in, int length) { + in.reset(); + final InstructionHolder[] result = new InstructionHolder[length]; + InstructionReader ir = new InstructionReader(in); + try { + ir.accept(new InstructionVisitor(null) { + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + result[currentAddress] = insnHolder; + } + + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = 1; + insnHolder.a = a; + result[currentAddress] = insnHolder; + } + + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = 2; + insnHolder.a = a; + insnHolder.b = b; + result[currentAddress] = insnHolder; + } + + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = 3; + insnHolder.a = a; + insnHolder.b = b; + insnHolder.c = c; + result[currentAddress] = insnHolder; + } + + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = 4; + insnHolder.a = a; + insnHolder.b = b; + insnHolder.c = c; + insnHolder.d = d; + result[currentAddress] = insnHolder; + } + + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = 5; + insnHolder.a = a; + insnHolder.b = b; + insnHolder.c = c; + insnHolder.d = d; + insnHolder.e = e; + result[currentAddress] = insnHolder; + } + + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + InstructionHolder insnHolder = new InstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.index = index; + insnHolder.target = target; + insnHolder.literal = literal; + insnHolder.registerCount = registerCount; + insnHolder.a = a; + result[currentAddress] = insnHolder; + } + + public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { + SparseSwitchPayloadInsntructionHolder insnHolder = new SparseSwitchPayloadInsntructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.keys = keys; + insnHolder.targets = targets; + result[currentAddress] = insnHolder; + } + + public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { + PackedSwitchPayloadInsntructionHolder insnHolder = new PackedSwitchPayloadInsntructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.firstKey = firstKey; + insnHolder.targets = targets; + result[currentAddress] = insnHolder; + } + + public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { + FillArrayDataPayloadInstructionHolder insnHolder = new FillArrayDataPayloadInstructionHolder(); + insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); + insnHolder.address = currentAddress; + insnHolder.opcode = opcode; + insnHolder.data = data; + insnHolder.size = size; + insnHolder.elementWidth = elementWidth; + result[currentAddress] = insnHolder; + } + }); + } catch (EOFException e) { + throw new RuntimeException(e); + } + return result; + } + + public final boolean compare() { + if (this.insnHolders1 == null && this.insnHolders2 == null) { + return true; + } + + if (this.insnHolders1 == null || this.insnHolders2 == null) { + return false; + } + + int currAddress1 = 0; + int currAddress2 = 0; + int insnHolderCount1 = 0; + int insnHolderCount2 = 0; + while (currAddress1 < insnHolders1.length && currAddress2 < insnHolders2.length) { + InstructionHolder insnHolder1 = null; + InstructionHolder insnHolder2 = null; + while (currAddress1 < insnHolders1.length && insnHolder1 == null) { + insnHolder1 = insnHolders1[currAddress1++]; + } + if (insnHolder1 != null) { + ++insnHolderCount1; + } else { + break; + } + while (currAddress2 < insnHolders2.length && insnHolder2 == null) { + insnHolder2 = insnHolders2[currAddress2++]; + } + if (insnHolder2 != null) { + ++insnHolderCount2; + } else { + break; + } + if (insnHolder1.opcode != insnHolder2.opcode) { + if (insnHolder1.opcode == Opcodes.CONST_STRING + && insnHolder2.opcode == Opcodes.CONST_STRING_JUMBO) { + if (!compareString(insnHolder1.index, insnHolder2.index)) { + return false; + } + } else + if (insnHolder1.opcode == Opcodes.CONST_STRING_JUMBO + && insnHolder2.opcode == Opcodes.CONST_STRING) { + if (!compareString(insnHolder1.index, insnHolder2.index)) { + return false; + } + } else { + return false; + } + } else { + if (!isSameInstruction(insnHolder1.address, insnHolder2.address)) { + return false; + } + } + } + while (currAddress1 < insnHolders1.length) { + if (insnHolders1[currAddress1++] != null) { + return false; + } + } + while (currAddress2 < insnHolders2.length) { + if (insnHolders2[currAddress2++] != null) { + return false; + } + } + return insnHolderCount1 == insnHolderCount2; + } + + public boolean isSameInstruction(int insnAddress1, int insnAddress2) { + InstructionHolder insnHolder1 = this.insnHolders1[insnAddress1]; + InstructionHolder insnHolder2 = this.insnHolders2[insnAddress2]; + if (insnHolder1 == null && insnHolder2 == null) { + return true; + } + if (insnHolder1 == null || insnHolder2 == null) { + return false; + } + if (insnHolder1.opcode != insnHolder2.opcode) { + return false; + } + int opcode = insnHolder1.opcode; + int insnFormat = insnHolder1.insnFormat; + switch (insnFormat) { + case InstructionCodec.INSN_FORMAT_10T: + case InstructionCodec.INSN_FORMAT_20T: + case InstructionCodec.INSN_FORMAT_21T: + case InstructionCodec.INSN_FORMAT_22T: + case InstructionCodec.INSN_FORMAT_30T: + case InstructionCodec.INSN_FORMAT_31T: { + return isSameInstruction(insnHolder1.target, insnHolder2.target); + } + case InstructionCodec.INSN_FORMAT_21C: + case InstructionCodec.INSN_FORMAT_22C: + case InstructionCodec.INSN_FORMAT_31C: + case InstructionCodec.INSN_FORMAT_35C: + case InstructionCodec.INSN_FORMAT_3RC: { + return compareIndex(opcode, insnHolder1.index, insnHolder2.index); + } + case InstructionCodec.INSN_FORMAT_PACKED_SWITCH_PAYLOAD: { + PackedSwitchPayloadInsntructionHolder specInsnHolder1 = (PackedSwitchPayloadInsntructionHolder) insnHolder1; + PackedSwitchPayloadInsntructionHolder specInsnHolder2 = (PackedSwitchPayloadInsntructionHolder) insnHolder2; + if (specInsnHolder1.firstKey != specInsnHolder2.firstKey) { + return false; + } + if (specInsnHolder1.targets.length != specInsnHolder2.targets.length) { + return false; + } + int targetCount = specInsnHolder1.targets.length; + for (int i = 0; i < targetCount; ++i) { + if (!isSameInstruction(specInsnHolder1.targets[i], specInsnHolder2.targets[i])) { + return false; + } + } + return true; + } + case InstructionCodec.INSN_FORMAT_SPARSE_SWITCH_PAYLOAD: { + SparseSwitchPayloadInsntructionHolder specInsnHolder1 = (SparseSwitchPayloadInsntructionHolder) insnHolder1; + SparseSwitchPayloadInsntructionHolder specInsnHolder2 = (SparseSwitchPayloadInsntructionHolder) insnHolder2; + if (CompareUtils.uArrCompare(specInsnHolder1.keys, specInsnHolder2.keys) != 0) { + return false; + } + if (specInsnHolder1.targets.length != specInsnHolder2.targets.length) { + return false; + } + int targetCount = specInsnHolder1.targets.length; + for (int i = 0; i < targetCount; ++i) { + if (!isSameInstruction(specInsnHolder1.targets[i], specInsnHolder2.targets[i])) { + return false; + } + } + return true; + } + case InstructionCodec.INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD: { + FillArrayDataPayloadInstructionHolder specInsnHolder1 = (FillArrayDataPayloadInstructionHolder) insnHolder1; + FillArrayDataPayloadInstructionHolder specInsnHolder2 = (FillArrayDataPayloadInstructionHolder) insnHolder2; + if (specInsnHolder1.elementWidth != specInsnHolder2.elementWidth) { + return false; + } + if (specInsnHolder1.size != specInsnHolder2.size) { + return false; + } + + int elementWidth = specInsnHolder1.elementWidth; + switch (elementWidth) { + case 1: { + byte[] array1 = (byte[]) specInsnHolder1.data; + byte[] array2 = (byte[]) specInsnHolder2.data; + return CompareUtils.uArrCompare(array1, array2) == 0; + } + case 2: { + short[] array1 = (short[]) specInsnHolder1.data; + short[] array2 = (short[]) specInsnHolder2.data; + return CompareUtils.uArrCompare(array1, array2) == 0; + } + case 4: { + int[] array1 = (int[]) specInsnHolder1.data; + int[] array2 = (int[]) specInsnHolder2.data; + return CompareUtils.uArrCompare(array1, array2) == 0; + } + case 8: { + long[] array1 = (long[]) specInsnHolder1.data; + long[] array2 = (long[]) specInsnHolder2.data; + return CompareUtils.sArrCompare(array1, array2) == 0; + } + default: { + throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); + } + } + } + default: { + if (insnHolder1.literal != insnHolder2.literal) { + return false; + } + if (insnHolder1.registerCount != insnHolder2.registerCount) { + return false; + } + if (insnHolder1.a != insnHolder2.a) { + return false; + } + if (insnHolder1.b != insnHolder2.b) { + return false; + } + if (insnHolder1.c != insnHolder2.c) { + return false; + } + if (insnHolder1.d != insnHolder2.d) { + return false; + } + if (insnHolder1.e != insnHolder2.e) { + return false; + } + return true; + } + } + } + + private boolean compareIndex(int opcode, int index1, int index2) { + switch (InstructionCodec.getInstructionIndexType(opcode)) { + case InstructionCodec.INDEX_TYPE_STRING_REF: { + return compareString(index1, index2); + } + case InstructionCodec.INDEX_TYPE_TYPE_REF: { + return compareType(index1, index2); + } + case InstructionCodec.INDEX_TYPE_FIELD_REF: { + return compareField(index1, index2); + } + case InstructionCodec.INDEX_TYPE_METHOD_REF: { + return compareMethod(index1, index2); + } + default: { + return index1 == index2; + } + } + } + + protected abstract boolean compareString(int stringIndex1, int stringIndex2); + + protected abstract boolean compareType(int typeIndex1, int typeIndex2); + + protected abstract boolean compareField(int fieldIndex1, int fieldIndex2); + + protected abstract boolean compareMethod(int methodIndex1, int methodIndex2); + + private static class InstructionHolder { + int insnFormat = InstructionCodec.INSN_FORMAT_UNKNOWN; + int address = -1; + int opcode = -1; + int index = 0; + int target = 0; + long literal = 0L; + int registerCount = 0; + int a = 0; + int b = 0; + int c = 0; + int d = 0; + int e = 0; + } + + private static class SparseSwitchPayloadInsntructionHolder extends InstructionHolder { + int[] keys = null; + int[] targets = null; + } + + private static class PackedSwitchPayloadInsntructionHolder extends InstructionHolder { + int firstKey = 0; + int[] targets = null; + } + + private static class FillArrayDataPayloadInstructionHolder extends InstructionHolder { + Object data = null; + int size = 0; + int elementWidth = 0; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionPromoter.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionPromoter.java new file mode 100644 index 00000000..41dfaa7d --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionPromoter.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dx.util.Hex; +import com.tencent.tinker.android.utils.SparseIntArray; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Created by tangyinsheng on 2016/8/11. + */ +public final class InstructionPromoter extends InstructionVisitor { + private final SparseIntArray addressMap = new SparseIntArray(); + + // Notice that the unit of currentPromotedAddress is not 'byte' + // but 'short' + private int currentPromotedAddress = 0; + + public InstructionPromoter() { + super(null); + } + + private void mapAddressIfNeeded(int currentAddress) { + if (currentAddress != this.currentPromotedAddress) { + addressMap.append(currentAddress, this.currentPromotedAddress); + } + } + + public int getPromotedAddress(int currentAddress) { + int index = addressMap.indexOfKey(currentAddress); + if (index < 0) { + return currentAddress; + } else { + return addressMap.valueAt(index); + } + } + + public int getPromotedAddressCount() { + return addressMap.size(); + } + + @Override + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.SPECIAL_FORMAT: + case Opcodes.NOP: + case Opcodes.RETURN_VOID: { + this.currentPromotedAddress += 1; + break; + } + case Opcodes.GOTO: { + int relativeTarget = InstructionCodec.getTarget(target, this.currentPromotedAddress); + if (relativeTarget != (byte) relativeTarget) { + if (relativeTarget != (short) relativeTarget) { + this.currentPromotedAddress += 3; + } else { + this.currentPromotedAddress += 2; + } + } else { + this.currentPromotedAddress += 1; + } + break; + } + case Opcodes.GOTO_16: { + int relativeTarget = InstructionCodec.getTarget(target, this.currentPromotedAddress); + if (relativeTarget != (short) relativeTarget) { + this.currentPromotedAddress += 3; + } else { + this.currentPromotedAddress += 2; + } + break; + } + case Opcodes.GOTO_32: { + this.currentPromotedAddress += 3; + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.CONST_STRING: { + if (index > 0xFFFF) { + this.currentPromotedAddress += 3; + } else { + this.currentPromotedAddress += 2; + } + break; + } + case Opcodes.CONST_STRING_JUMBO: { + this.currentPromotedAddress += 3; + break; + } + case Opcodes.CONST_4: + case Opcodes.MOVE_RESULT: + case Opcodes.MOVE_RESULT_WIDE: + case Opcodes.MOVE_RESULT_OBJECT: + case Opcodes.MOVE_EXCEPTION: + case Opcodes.RETURN: + case Opcodes.RETURN_WIDE: + case Opcodes.RETURN_OBJECT: + case Opcodes.MONITOR_ENTER: + case Opcodes.MONITOR_EXIT: + case Opcodes.THROW: { + this.currentPromotedAddress += 1; + break; + } + case Opcodes.IF_EQZ: + case Opcodes.IF_NEZ: + case Opcodes.IF_LTZ: + case Opcodes.IF_GEZ: + case Opcodes.IF_GTZ: + case Opcodes.IF_LEZ: + case Opcodes.CONST_16: + case Opcodes.CONST_WIDE_16: + case Opcodes.CONST_HIGH16: + case Opcodes.CONST_WIDE_HIGH16: + case Opcodes.CONST_CLASS: + case Opcodes.CHECK_CAST: + case Opcodes.NEW_INSTANCE: + case Opcodes.SGET: + case Opcodes.SGET_WIDE: + case Opcodes.SGET_OBJECT: + case Opcodes.SGET_BOOLEAN: + case Opcodes.SGET_BYTE: + case Opcodes.SGET_CHAR: + case Opcodes.SGET_SHORT: + case Opcodes.SPUT: + case Opcodes.SPUT_WIDE: + case Opcodes.SPUT_OBJECT: + case Opcodes.SPUT_BOOLEAN: + case Opcodes.SPUT_BYTE: + case Opcodes.SPUT_CHAR: + case Opcodes.SPUT_SHORT: { + this.currentPromotedAddress += 2; + break; + } + case Opcodes.CONST: + case Opcodes.CONST_WIDE_32: + case Opcodes.FILL_ARRAY_DATA: + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + case Opcodes.CONST_WIDE: { + this.currentPromotedAddress += 5; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.MOVE: + case Opcodes.MOVE_WIDE: + case Opcodes.MOVE_OBJECT: + case Opcodes.ARRAY_LENGTH: + case Opcodes.NEG_INT: + case Opcodes.NOT_INT: + case Opcodes.NEG_LONG: + case Opcodes.NOT_LONG: + case Opcodes.NEG_FLOAT: + case Opcodes.NEG_DOUBLE: + case Opcodes.INT_TO_LONG: + case Opcodes.INT_TO_FLOAT: + case Opcodes.INT_TO_DOUBLE: + case Opcodes.LONG_TO_INT: + case Opcodes.LONG_TO_FLOAT: + case Opcodes.LONG_TO_DOUBLE: + case Opcodes.FLOAT_TO_INT: + case Opcodes.FLOAT_TO_LONG: + case Opcodes.FLOAT_TO_DOUBLE: + case Opcodes.DOUBLE_TO_INT: + case Opcodes.DOUBLE_TO_LONG: + case Opcodes.DOUBLE_TO_FLOAT: + case Opcodes.INT_TO_BYTE: + case Opcodes.INT_TO_CHAR: + case Opcodes.INT_TO_SHORT: + case Opcodes.ADD_INT_2ADDR: + case Opcodes.SUB_INT_2ADDR: + case Opcodes.MUL_INT_2ADDR: + case Opcodes.DIV_INT_2ADDR: + case Opcodes.REM_INT_2ADDR: + case Opcodes.AND_INT_2ADDR: + case Opcodes.OR_INT_2ADDR: + case Opcodes.XOR_INT_2ADDR: + case Opcodes.SHL_INT_2ADDR: + case Opcodes.SHR_INT_2ADDR: + case Opcodes.USHR_INT_2ADDR: + case Opcodes.ADD_LONG_2ADDR: + case Opcodes.SUB_LONG_2ADDR: + case Opcodes.MUL_LONG_2ADDR: + case Opcodes.DIV_LONG_2ADDR: + case Opcodes.REM_LONG_2ADDR: + case Opcodes.AND_LONG_2ADDR: + case Opcodes.OR_LONG_2ADDR: + case Opcodes.XOR_LONG_2ADDR: + case Opcodes.SHL_LONG_2ADDR: + case Opcodes.SHR_LONG_2ADDR: + case Opcodes.USHR_LONG_2ADDR: + case Opcodes.ADD_FLOAT_2ADDR: + case Opcodes.SUB_FLOAT_2ADDR: + case Opcodes.MUL_FLOAT_2ADDR: + case Opcodes.DIV_FLOAT_2ADDR: + case Opcodes.REM_FLOAT_2ADDR: + case Opcodes.ADD_DOUBLE_2ADDR: + case Opcodes.SUB_DOUBLE_2ADDR: + case Opcodes.MUL_DOUBLE_2ADDR: + case Opcodes.DIV_DOUBLE_2ADDR: + case Opcodes.REM_DOUBLE_2ADDR: { + this.currentPromotedAddress += 1; + break; + } + case Opcodes.MOVE_FROM16: + case Opcodes.MOVE_WIDE_FROM16: + case Opcodes.MOVE_OBJECT_FROM16: { + this.currentPromotedAddress += 2; + break; + } + case Opcodes.ADD_INT_LIT8: + case Opcodes.RSUB_INT_LIT8: + case Opcodes.MUL_INT_LIT8: + case Opcodes.DIV_INT_LIT8: + case Opcodes.REM_INT_LIT8: + case Opcodes.AND_INT_LIT8: + case Opcodes.OR_INT_LIT8: + case Opcodes.XOR_INT_LIT8: + case Opcodes.SHL_INT_LIT8: + case Opcodes.SHR_INT_LIT8: + case Opcodes.USHR_INT_LIT8: + case Opcodes.IF_EQ: + case Opcodes.IF_NE: + case Opcodes.IF_LT: + case Opcodes.IF_GE: + case Opcodes.IF_GT: + case Opcodes.IF_LE: + case Opcodes.ADD_INT_LIT16: + case Opcodes.RSUB_INT: + case Opcodes.MUL_INT_LIT16: + case Opcodes.DIV_INT_LIT16: + case Opcodes.REM_INT_LIT16: + case Opcodes.AND_INT_LIT16: + case Opcodes.OR_INT_LIT16: + case Opcodes.XOR_INT_LIT16: + case Opcodes.INSTANCE_OF: + case Opcodes.NEW_ARRAY: + case Opcodes.IGET: + case Opcodes.IGET_WIDE: + case Opcodes.IGET_OBJECT: + case Opcodes.IGET_BOOLEAN: + case Opcodes.IGET_BYTE: + case Opcodes.IGET_CHAR: + case Opcodes.IGET_SHORT: + case Opcodes.IPUT: + case Opcodes.IPUT_WIDE: + case Opcodes.IPUT_OBJECT: + case Opcodes.IPUT_BOOLEAN: + case Opcodes.IPUT_BYTE: + case Opcodes.IPUT_CHAR: + case Opcodes.IPUT_SHORT: { + this.currentPromotedAddress += 2; + break; + } + case Opcodes.MOVE_16: + case Opcodes.MOVE_WIDE_16: + case Opcodes.MOVE_OBJECT_16: + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.CMPL_FLOAT: + case Opcodes.CMPG_FLOAT: + case Opcodes.CMPL_DOUBLE: + case Opcodes.CMPG_DOUBLE: + case Opcodes.CMP_LONG: + case Opcodes.AGET: + case Opcodes.AGET_WIDE: + case Opcodes.AGET_OBJECT: + case Opcodes.AGET_BOOLEAN: + case Opcodes.AGET_BYTE: + case Opcodes.AGET_CHAR: + case Opcodes.AGET_SHORT: + case Opcodes.APUT: + case Opcodes.APUT_WIDE: + case Opcodes.APUT_OBJECT: + case Opcodes.APUT_BOOLEAN: + case Opcodes.APUT_BYTE: + case Opcodes.APUT_CHAR: + case Opcodes.APUT_SHORT: + case Opcodes.ADD_INT: + case Opcodes.SUB_INT: + case Opcodes.MUL_INT: + case Opcodes.DIV_INT: + case Opcodes.REM_INT: + case Opcodes.AND_INT: + case Opcodes.OR_INT: + case Opcodes.XOR_INT: + case Opcodes.SHL_INT: + case Opcodes.SHR_INT: + case Opcodes.USHR_INT: + case Opcodes.ADD_LONG: + case Opcodes.SUB_LONG: + case Opcodes.MUL_LONG: + case Opcodes.DIV_LONG: + case Opcodes.REM_LONG: + case Opcodes.AND_LONG: + case Opcodes.OR_LONG: + case Opcodes.XOR_LONG: + case Opcodes.SHL_LONG: + case Opcodes.SHR_LONG: + case Opcodes.USHR_LONG: + case Opcodes.ADD_FLOAT: + case Opcodes.SUB_FLOAT: + case Opcodes.MUL_FLOAT: + case Opcodes.DIV_FLOAT: + case Opcodes.REM_FLOAT: + case Opcodes.ADD_DOUBLE: + case Opcodes.SUB_DOUBLE: + case Opcodes.MUL_DOUBLE: + case Opcodes.DIV_DOUBLE: + case Opcodes.REM_DOUBLE: { + this.currentPromotedAddress += 2; + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + mapAddressIfNeeded(currentAddress); + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY_RANGE: + case Opcodes.INVOKE_VIRTUAL_RANGE: + case Opcodes.INVOKE_SUPER_RANGE: + case Opcodes.INVOKE_DIRECT_RANGE: + case Opcodes.INVOKE_STATIC_RANGE: + case Opcodes.INVOKE_INTERFACE_RANGE: { + this.currentPromotedAddress += 3; + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + @Override + public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { + mapAddressIfNeeded(currentAddress); + + this.currentPromotedAddress += 2; + + this.currentPromotedAddress += keys.length * 2; + + this.currentPromotedAddress += targets.length * 2; + } + + @Override + public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { + mapAddressIfNeeded(currentAddress); + + this.currentPromotedAddress += 2 + 2; + + this.currentPromotedAddress += targets.length * 2; + } + + @Override + public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { + mapAddressIfNeeded(currentAddress); + + this.currentPromotedAddress += 2 + 2; + + switch (elementWidth) { + case 1: { + int length = ((byte[]) data).length; + this.currentPromotedAddress += (length >> 1) + (length & 1); + break; + } + case 2: { + this.currentPromotedAddress += ((short[]) data).length * 1; + break; + } + case 4: { + this.currentPromotedAddress += ((int[]) data).length * 2; + break; + } + case 8: { + this.currentPromotedAddress += ((long[]) data).length * 4; + break; + } + default: { + throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); + } + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionReader.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionReader.java new file mode 100644 index 00000000..de8ef5aa --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionReader.java @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dx.util.Hex; + +import java.io.EOFException; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Created by tangyinsheng on 2016/5/26. + */ +public final class InstructionReader { + private final ShortArrayCodeInput codeIn; + + public InstructionReader(ShortArrayCodeInput in) { + this.codeIn = in; + } + + public void accept(InstructionVisitor iv) throws EOFException { + codeIn.reset(); + while (codeIn.hasMore()) { + int currentAddress = codeIn.cursor(); + int opcodeUnit = codeIn.read(); + int opcodeForSwitch = Opcodes.extractOpcodeFromUnit(opcodeUnit); + switch (opcodeForSwitch) { + case Opcodes.SPECIAL_FORMAT: { + iv.visitZeroRegisterInsn(currentAddress, opcodeUnit, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L); + break; + } + case Opcodes.GOTO: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int target = (byte) InstructionCodec.byte1(opcodeUnit); // sign-extend + iv.visitZeroRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, 0L); + break; + } + case Opcodes.NOP: + case Opcodes.RETURN_VOID: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int literal = InstructionCodec.byte1(opcodeUnit); // should be zero + iv.visitZeroRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal); + break; + } + case Opcodes.CONST_4: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.nibble2(opcodeUnit); + int literal = (InstructionCodec.nibble3(opcodeUnit) << 28) >> 28; // sign-extend + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a); + break; + } + case Opcodes.MOVE_RESULT: + case Opcodes.MOVE_RESULT_WIDE: + case Opcodes.MOVE_RESULT_OBJECT: + case Opcodes.MOVE_EXCEPTION: + case Opcodes.RETURN: + case Opcodes.RETURN_WIDE: + case Opcodes.RETURN_OBJECT: + case Opcodes.MONITOR_ENTER: + case Opcodes.MONITOR_EXIT: + case Opcodes.THROW: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L, a); + break; + } + case Opcodes.MOVE: + case Opcodes.MOVE_WIDE: + case Opcodes.MOVE_OBJECT: + case Opcodes.ARRAY_LENGTH: + case Opcodes.NEG_INT: + case Opcodes.NOT_INT: + case Opcodes.NEG_LONG: + case Opcodes.NOT_LONG: + case Opcodes.NEG_FLOAT: + case Opcodes.NEG_DOUBLE: + case Opcodes.INT_TO_LONG: + case Opcodes.INT_TO_FLOAT: + case Opcodes.INT_TO_DOUBLE: + case Opcodes.LONG_TO_INT: + case Opcodes.LONG_TO_FLOAT: + case Opcodes.LONG_TO_DOUBLE: + case Opcodes.FLOAT_TO_INT: + case Opcodes.FLOAT_TO_LONG: + case Opcodes.FLOAT_TO_DOUBLE: + case Opcodes.DOUBLE_TO_INT: + case Opcodes.DOUBLE_TO_LONG: + case Opcodes.DOUBLE_TO_FLOAT: + case Opcodes.INT_TO_BYTE: + case Opcodes.INT_TO_CHAR: + case Opcodes.INT_TO_SHORT: + case Opcodes.ADD_INT_2ADDR: + case Opcodes.SUB_INT_2ADDR: + case Opcodes.MUL_INT_2ADDR: + case Opcodes.DIV_INT_2ADDR: + case Opcodes.REM_INT_2ADDR: + case Opcodes.AND_INT_2ADDR: + case Opcodes.OR_INT_2ADDR: + case Opcodes.XOR_INT_2ADDR: + case Opcodes.SHL_INT_2ADDR: + case Opcodes.SHR_INT_2ADDR: + case Opcodes.USHR_INT_2ADDR: + case Opcodes.ADD_LONG_2ADDR: + case Opcodes.SUB_LONG_2ADDR: + case Opcodes.MUL_LONG_2ADDR: + case Opcodes.DIV_LONG_2ADDR: + case Opcodes.REM_LONG_2ADDR: + case Opcodes.AND_LONG_2ADDR: + case Opcodes.OR_LONG_2ADDR: + case Opcodes.XOR_LONG_2ADDR: + case Opcodes.SHL_LONG_2ADDR: + case Opcodes.SHR_LONG_2ADDR: + case Opcodes.USHR_LONG_2ADDR: + case Opcodes.ADD_FLOAT_2ADDR: + case Opcodes.SUB_FLOAT_2ADDR: + case Opcodes.MUL_FLOAT_2ADDR: + case Opcodes.DIV_FLOAT_2ADDR: + case Opcodes.REM_FLOAT_2ADDR: + case Opcodes.ADD_DOUBLE_2ADDR: + case Opcodes.SUB_DOUBLE_2ADDR: + case Opcodes.MUL_DOUBLE_2ADDR: + case Opcodes.DIV_DOUBLE_2ADDR: + case Opcodes.REM_DOUBLE_2ADDR: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.nibble2(opcodeUnit); + int b = InstructionCodec.nibble3(opcodeUnit); + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L, a, b); + break; + } + case Opcodes.GOTO_16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int literal = InstructionCodec.byte1(opcodeUnit); // should be zero + int target = (short) codeIn.read(); // sign-extend + iv.visitZeroRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, literal); + break; + } + case Opcodes.CONST_STRING: + case Opcodes.CONST_CLASS: + case Opcodes.CHECK_CAST: + case Opcodes.NEW_INSTANCE: + case Opcodes.SGET: + case Opcodes.SGET_WIDE: + case Opcodes.SGET_OBJECT: + case Opcodes.SGET_BOOLEAN: + case Opcodes.SGET_BYTE: + case Opcodes.SGET_CHAR: + case Opcodes.SGET_SHORT: + case Opcodes.SPUT: + case Opcodes.SPUT_WIDE: + case Opcodes.SPUT_OBJECT: + case Opcodes.SPUT_BOOLEAN: + case Opcodes.SPUT_BYTE: + case Opcodes.SPUT_CHAR: + case Opcodes.SPUT_SHORT: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int index = codeIn.read(); + int indexType = InstructionCodec.getInstructionIndexType(opcode); + iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); + break; + } + case Opcodes.CONST_HIGH16: + case Opcodes.CONST_WIDE_HIGH16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + long literal = (short) codeIn.read(); // sign-extend + + /* + * Format 21h decodes differently depending on the opcode, + * because the "signed hat" might represent either a 32- + * or 64- bit value. + */ + literal <<= (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a); + + break; + } + case Opcodes.CONST_16: + case Opcodes.CONST_WIDE_16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int literal = (short) codeIn.read(); // sign-extend + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a); + break; + } + case Opcodes.IF_EQZ: + case Opcodes.IF_NEZ: + case Opcodes.IF_LTZ: + case Opcodes.IF_GEZ: + case Opcodes.IF_GTZ: + case Opcodes.IF_LEZ: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int target = (short) codeIn.read(); // sign-extend + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, 0L, a); + break; + } + case Opcodes.ADD_INT_LIT8: + case Opcodes.RSUB_INT_LIT8: + case Opcodes.MUL_INT_LIT8: + case Opcodes.DIV_INT_LIT8: + case Opcodes.REM_INT_LIT8: + case Opcodes.AND_INT_LIT8: + case Opcodes.OR_INT_LIT8: + case Opcodes.XOR_INT_LIT8: + case Opcodes.SHL_INT_LIT8: + case Opcodes.SHR_INT_LIT8: + case Opcodes.USHR_INT_LIT8: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int bc = codeIn.read(); + int b = InstructionCodec.byte0(bc); + int literal = (byte) InstructionCodec.byte1(bc); // sign-extend + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a, b); + break; + } + case Opcodes.INSTANCE_OF: + case Opcodes.NEW_ARRAY: + case Opcodes.IGET: + case Opcodes.IGET_WIDE: + case Opcodes.IGET_OBJECT: + case Opcodes.IGET_BOOLEAN: + case Opcodes.IGET_BYTE: + case Opcodes.IGET_CHAR: + case Opcodes.IGET_SHORT: + case Opcodes.IPUT: + case Opcodes.IPUT_WIDE: + case Opcodes.IPUT_OBJECT: + case Opcodes.IPUT_BOOLEAN: + case Opcodes.IPUT_BYTE: + case Opcodes.IPUT_CHAR: + case Opcodes.IPUT_SHORT: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.nibble2(opcodeUnit); + int b = InstructionCodec.nibble3(opcodeUnit); + int index = codeIn.read(); + int indexType = InstructionCodec.getInstructionIndexType(opcode); + iv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b); + break; + } + case Opcodes.ADD_INT_LIT16: + case Opcodes.RSUB_INT: + case Opcodes.MUL_INT_LIT16: + case Opcodes.DIV_INT_LIT16: + case Opcodes.REM_INT_LIT16: + case Opcodes.AND_INT_LIT16: + case Opcodes.OR_INT_LIT16: + case Opcodes.XOR_INT_LIT16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.nibble2(opcodeUnit); + int b = InstructionCodec.nibble3(opcodeUnit); + int literal = (short) codeIn.read(); // sign-extend + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a, b); + break; + } + case Opcodes.IF_EQ: + case Opcodes.IF_NE: + case Opcodes.IF_LT: + case Opcodes.IF_GE: + case Opcodes.IF_GT: + case Opcodes.IF_LE: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.nibble2(opcodeUnit); + int b = InstructionCodec.nibble3(opcodeUnit); + int target = (short) codeIn.read(); // sign-extend + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, 0L, a, b); + break; + } + case Opcodes.MOVE_FROM16: + case Opcodes.MOVE_WIDE_FROM16: + case Opcodes.MOVE_OBJECT_FROM16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int b = codeIn.read(); + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L, a, b); + break; + } + case Opcodes.CMPL_FLOAT: + case Opcodes.CMPG_FLOAT: + case Opcodes.CMPL_DOUBLE: + case Opcodes.CMPG_DOUBLE: + case Opcodes.CMP_LONG: + case Opcodes.AGET: + case Opcodes.AGET_WIDE: + case Opcodes.AGET_OBJECT: + case Opcodes.AGET_BOOLEAN: + case Opcodes.AGET_BYTE: + case Opcodes.AGET_CHAR: + case Opcodes.AGET_SHORT: + case Opcodes.APUT: + case Opcodes.APUT_WIDE: + case Opcodes.APUT_OBJECT: + case Opcodes.APUT_BOOLEAN: + case Opcodes.APUT_BYTE: + case Opcodes.APUT_CHAR: + case Opcodes.APUT_SHORT: + case Opcodes.ADD_INT: + case Opcodes.SUB_INT: + case Opcodes.MUL_INT: + case Opcodes.DIV_INT: + case Opcodes.REM_INT: + case Opcodes.AND_INT: + case Opcodes.OR_INT: + case Opcodes.XOR_INT: + case Opcodes.SHL_INT: + case Opcodes.SHR_INT: + case Opcodes.USHR_INT: + case Opcodes.ADD_LONG: + case Opcodes.SUB_LONG: + case Opcodes.MUL_LONG: + case Opcodes.DIV_LONG: + case Opcodes.REM_LONG: + case Opcodes.AND_LONG: + case Opcodes.OR_LONG: + case Opcodes.XOR_LONG: + case Opcodes.SHL_LONG: + case Opcodes.SHR_LONG: + case Opcodes.USHR_LONG: + case Opcodes.ADD_FLOAT: + case Opcodes.SUB_FLOAT: + case Opcodes.MUL_FLOAT: + case Opcodes.DIV_FLOAT: + case Opcodes.REM_FLOAT: + case Opcodes.ADD_DOUBLE: + case Opcodes.SUB_DOUBLE: + case Opcodes.MUL_DOUBLE: + case Opcodes.DIV_DOUBLE: + case Opcodes.REM_DOUBLE: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int bc = codeIn.read(); + int b = InstructionCodec.byte0(bc); + int c = InstructionCodec.byte1(bc); + iv.visitThreeRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, 0L, a, b, c); + break; + } + case Opcodes.GOTO_32: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int literal = InstructionCodec.byte1(opcodeUnit); // should be zero + int target = codeIn.readInt(); + iv.visitZeroRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, currentAddress + target, literal); + break; + } + case Opcodes.CONST_STRING_JUMBO: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int index = codeIn.readInt(); + int indexType = InstructionCodec.getInstructionIndexType(opcode); + iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); + break; + } + case Opcodes.CONST: + case Opcodes.CONST_WIDE_32: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int literal = codeIn.readInt(); + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a); + break; + } + case Opcodes.FILL_ARRAY_DATA: + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + int target = currentAddress + codeIn.readInt(); + + /* + * Switch instructions need to "forward" their addresses to their + * payload target instructions. + */ + switch (opcode) { + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + // plus 1 means when we actually lookup the currentAddress + // by (payload insn address + 1), + codeIn.setBaseAddress(target + 1, currentAddress); + break; + } + } + + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, target, 0L, a); + break; + } + case Opcodes.MOVE_16: + case Opcodes.MOVE_WIDE_16: + case Opcodes.MOVE_OBJECT_16: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int literal = InstructionCodec.byte1(opcodeUnit); // should be zero + int a = codeIn.read(); + int b = codeIn.read(); + iv.visitTwoRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a, b); + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int e = InstructionCodec.nibble2(opcodeUnit); + int registerCount = InstructionCodec.nibble3(opcodeUnit); + int index = codeIn.read(); + int abcd = codeIn.read(); + int a = InstructionCodec.nibble0(abcd); + int b = InstructionCodec.nibble1(abcd); + int c = InstructionCodec.nibble2(abcd); + int d = InstructionCodec.nibble3(abcd); + int indexType = InstructionCodec.getInstructionIndexType(opcode); + + switch (registerCount) { + case 0: { + iv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L); + break; + } + case 1: { + iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); + break; + } + case 2: { + iv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b); + break; + } + case 3: { + iv.visitThreeRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c); + break; + } + case 4: { + iv.visitFourRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d); + break; + } + case 5: { + iv.visitFiveRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d, e); + break; + } + default: { + throw new DexException("bogus registerCount: " + Hex.uNibble(registerCount)); + } + } + break; + } + case Opcodes.FILLED_NEW_ARRAY_RANGE: + case Opcodes.INVOKE_VIRTUAL_RANGE: + case Opcodes.INVOKE_SUPER_RANGE: + case Opcodes.INVOKE_DIRECT_RANGE: + case Opcodes.INVOKE_STATIC_RANGE: + case Opcodes.INVOKE_INTERFACE_RANGE: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int registerCount = InstructionCodec.byte1(opcodeUnit); + int index = codeIn.read(); + int a = codeIn.read(); + int indexType = InstructionCodec.getInstructionIndexType(opcode); + iv.visitRegisterRangeInsn(currentAddress, opcode, index, indexType, 0, 0L, a, registerCount); + break; + } + case Opcodes.CONST_WIDE: { + int opcode = InstructionCodec.byte0(opcodeUnit); + int a = InstructionCodec.byte1(opcodeUnit); + long literal = codeIn.readLong(); + iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, 0, literal, a); + break; + } + case Opcodes.FILL_ARRAY_DATA_PAYLOAD: { + int elementWidth = codeIn.read(); + int size = codeIn.readInt(); + + switch (elementWidth) { + case 1: { + byte[] array = new byte[size]; + boolean even = true; + for (int i = 0, value = 0; i < size; ++i, even = !even) { + if (even) { + value = codeIn.read(); + } + array[i] = (byte) (value & 0xff); + value >>= 8; + } + iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 1); + break; + } + case 2: { + short[] array = new short[size]; + for (int i = 0; i < size; i++) { + array[i] = (short) codeIn.read(); + } + iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 2); + break; + } + case 4: { + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = codeIn.readInt(); + } + iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 4); + break; + } + case 8: { + long[] array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = codeIn.readLong(); + } + iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 8); + break; + } + default: { + throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); + } + } + break; + } + case Opcodes.PACKED_SWITCH_PAYLOAD: { + int baseAddress = codeIn.baseAddressForCursor(); + int size = codeIn.read(); + int firstKey = codeIn.readInt(); + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + targets[i] = baseAddress + codeIn.readInt(); + } + iv.visitPackedSwitchPayloadInsn(currentAddress, opcodeUnit, firstKey, targets); + break; + } + case Opcodes.SPARSE_SWITCH_PAYLOAD: { + int baseAddress = codeIn.baseAddressForCursor(); + int size = codeIn.read(); + int[] keys = new int[size]; + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + keys[i] = codeIn.readInt(); + } + + for (int i = 0; i < size; i++) { + targets[i] = baseAddress + codeIn.readInt(); + } + + iv.visitSparseSwitchPayloadInsn(currentAddress, opcodeUnit, keys, targets); + break; + } + default: { + throw new IllegalStateException("Unknown opcode: " + Hex.u4(opcodeForSwitch)); + } + } + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionVisitor.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionVisitor.java new file mode 100644 index 00000000..761f6ff5 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionVisitor.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Created by tangyinsheng on 2016/5/26. + */ +public class InstructionVisitor { + private final InstructionVisitor prevIv; + + public InstructionVisitor(InstructionVisitor iv) { + this.prevIv = iv; + } + + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + if (prevIv != null) { + prevIv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, target, literal); + } + } + + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + if (prevIv != null) { + prevIv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a); + } + } + + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + if (prevIv != null) { + prevIv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b); + } + } + + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + if (prevIv != null) { + prevIv.visitThreeRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c); + } + } + + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + if (prevIv != null) { + prevIv.visitFourRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c, d); + } + } + + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + if (prevIv != null) { + prevIv.visitFiveRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c, d, e); + } + } + + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + if (prevIv != null) { + prevIv.visitRegisterRangeInsn(currentAddress, opcode, index, indexType, target, literal, a, registerCount); + } + } + + public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { + if (prevIv != null) { + prevIv.visitSparseSwitchPayloadInsn(currentAddress, opcode, keys, targets); + } + } + + public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { + if (prevIv != null) { + prevIv.visitPackedSwitchPayloadInsn(currentAddress, opcode, firstKey, targets); + } + } + + public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { + if (prevIv != null) { + prevIv.visitFillArrayDataPayloadInsn(currentAddress, opcode, data, size, elementWidth); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionWriter.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionWriter.java new file mode 100644 index 00000000..ea3ee8fa --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionWriter.java @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dx.util.Hex; + +/** + * *** This file is NOT a part of AOSP. *** + * + * Created by tangyinsheng on 2016/5/27. + */ +public final class InstructionWriter extends InstructionVisitor { + private final ShortArrayCodeOutput codeOut; + private final InstructionPromoter insnPromoter; + private final boolean hasPromoter; + + public InstructionWriter(ShortArrayCodeOutput codeOut, InstructionPromoter ipmo) { + super(null); + this.codeOut = codeOut; + this.insnPromoter = ipmo; + this.hasPromoter = (ipmo != null); + } + + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + if (this.hasPromoter) { + target = this.insnPromoter.getPromotedAddress(target); + } + + switch (opcode) { + case Opcodes.SPECIAL_FORMAT: + case Opcodes.NOP: + case Opcodes.RETURN_VOID: { + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit); + break; + } + case Opcodes.GOTO: { + if (this.hasPromoter) { + int relativeTarget = InstructionCodec.getTarget(target, codeOut.cursor()); + if (relativeTarget != (byte) relativeTarget) { + if (relativeTarget != (short) relativeTarget) { + short opcodeUnit = (short) Opcodes.GOTO_32; + codeOut.write(opcodeUnit, InstructionCodec.unit0(relativeTarget), InstructionCodec.unit1(relativeTarget)); + } else { + short shortRelativeTarget = (short) relativeTarget; + short opcodeUnit = (short) Opcodes.GOTO_16; + codeOut.write(opcodeUnit, shortRelativeTarget); + } + } else { + relativeTarget &= 0xFF; + codeOut.write(InstructionCodec.codeUnit(opcode, relativeTarget)); + } + } else { + int relativeTarget = InstructionCodec.getTargetByte(target, codeOut.cursor()); + codeOut.write(InstructionCodec.codeUnit(opcode, relativeTarget)); + } + break; + } + case Opcodes.GOTO_16: { + if (this.hasPromoter) { + int relativeTarget = InstructionCodec.getTarget(target, codeOut.cursor()); + if (relativeTarget != (short) relativeTarget) { + short opcodeUnit = (short) Opcodes.GOTO_32; + codeOut.write(opcodeUnit, InstructionCodec.unit0(relativeTarget), InstructionCodec.unit1(relativeTarget)); + } else { + short shortRelativeTarget = (short) relativeTarget; + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit, shortRelativeTarget); + } + } else { + short relativeTarget = InstructionCodec.getTargetUnit(target, codeOut.cursor()); + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit, relativeTarget); + } + break; + } + case Opcodes.GOTO_32: { + int relativeTarget = InstructionCodec.getTarget(target, codeOut.cursor()); + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit, InstructionCodec.unit0(relativeTarget), InstructionCodec.unit1(relativeTarget)); + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(0, 0) + ), + indexUnit, + InstructionCodec.codeUnit(0, 0, 0, 0) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + if (this.hasPromoter) { + target = this.insnPromoter.getPromotedAddress(target); + } + + switch (opcode) { + case Opcodes.CONST_4: { + short opcodeUnit = (short) opcode; + codeOut.write( + InstructionCodec.codeUnit( + opcodeUnit, + InstructionCodec.makeByte(a, InstructionCodec.getLiteralNibble(literal)) + ) + ); + break; + } + case Opcodes.MOVE_RESULT: + case Opcodes.MOVE_RESULT_WIDE: + case Opcodes.MOVE_RESULT_OBJECT: + case Opcodes.MOVE_EXCEPTION: + case Opcodes.RETURN: + case Opcodes.RETURN_WIDE: + case Opcodes.RETURN_OBJECT: + case Opcodes.MONITOR_ENTER: + case Opcodes.MONITOR_EXIT: + case Opcodes.THROW: { + codeOut.write(InstructionCodec.codeUnit(opcode, a)); + break; + } + case Opcodes.IF_EQZ: + case Opcodes.IF_NEZ: + case Opcodes.IF_LTZ: + case Opcodes.IF_GEZ: + case Opcodes.IF_GTZ: + case Opcodes.IF_LEZ: { + short relativeTarget = InstructionCodec.getTargetUnit(target, codeOut.cursor()); + codeOut.write(InstructionCodec.codeUnit(opcode, a), relativeTarget); + break; + } + case Opcodes.CONST_16: + case Opcodes.CONST_WIDE_16: { + codeOut.write(InstructionCodec.codeUnit(opcode, a), InstructionCodec.getLiteralUnit(literal)); + break; + } + case Opcodes.CONST_HIGH16: + case Opcodes.CONST_WIDE_HIGH16: { + int shift = (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + short literalShifted = (short) (literal >> shift); + codeOut.write(InstructionCodec.codeUnit(opcode, a), literalShifted); + break; + } + case Opcodes.CONST_STRING: { + if (this.hasPromoter) { + if (index > 0xFFFF) { + codeOut.write( + InstructionCodec.codeUnit(Opcodes.CONST_STRING_JUMBO, a), + InstructionCodec.unit0(index), + InstructionCodec.unit1(index) + ); + } else { + short indexUnit = (short) index; + codeOut.write(InstructionCodec.codeUnit(opcode, a), indexUnit); + } + } else { + if (index > 0xFFFF) { + throw new DexException( + "string index out of bound: " + + Hex.u4(index) + + ", perhaps you need to enable force jumbo mode." + ); + } + short indexUnit = (short) index; + codeOut.write(InstructionCodec.codeUnit(opcode, a), indexUnit); + } + break; + } + case Opcodes.CONST_CLASS: + case Opcodes.CHECK_CAST: + case Opcodes.NEW_INSTANCE: + case Opcodes.SGET: + case Opcodes.SGET_WIDE: + case Opcodes.SGET_OBJECT: + case Opcodes.SGET_BOOLEAN: + case Opcodes.SGET_BYTE: + case Opcodes.SGET_CHAR: + case Opcodes.SGET_SHORT: + case Opcodes.SPUT: + case Opcodes.SPUT_WIDE: + case Opcodes.SPUT_OBJECT: + case Opcodes.SPUT_BOOLEAN: + case Opcodes.SPUT_BYTE: + case Opcodes.SPUT_CHAR: + case Opcodes.SPUT_SHORT: { + short indexUnit = (short) index; + codeOut.write(InstructionCodec.codeUnit(opcode, a), indexUnit); + break; + } + case Opcodes.CONST: + case Opcodes.CONST_WIDE_32: { + int literalInt = InstructionCodec.getLiteralInt(literal); + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.unit0(literalInt), + InstructionCodec.unit1(literalInt) + ); + break; + } + case Opcodes.FILL_ARRAY_DATA: + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + /* + * Switch instructions need to "forward" their addresses to their + * payload target instructions. + */ + switch (opcode) { + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + codeOut.setBaseAddress(target, codeOut.cursor()); + break; + } + } + + int relativeTarget = InstructionCodec.getTarget(target, codeOut.cursor()); + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.unit0(relativeTarget), + InstructionCodec.unit1(relativeTarget) + ); + break; + } + case Opcodes.CONST_STRING_JUMBO: { + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.unit0(index), + InstructionCodec.unit1(index) + ); + break; + } + case Opcodes.CONST_WIDE: { + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.unit0(literal), + InstructionCodec.unit1(literal), + InstructionCodec.unit2(literal), + InstructionCodec.unit3(literal) + ); + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(0, 1) + ), + indexUnit, + InstructionCodec.codeUnit(a, 0, 0, 0) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + if (this.hasPromoter) { + target = this.insnPromoter.getPromotedAddress(target); + } + + switch (opcode) { + case Opcodes.MOVE: + case Opcodes.MOVE_WIDE: + case Opcodes.MOVE_OBJECT: + case Opcodes.ARRAY_LENGTH: + case Opcodes.NEG_INT: + case Opcodes.NOT_INT: + case Opcodes.NEG_LONG: + case Opcodes.NOT_LONG: + case Opcodes.NEG_FLOAT: + case Opcodes.NEG_DOUBLE: + case Opcodes.INT_TO_LONG: + case Opcodes.INT_TO_FLOAT: + case Opcodes.INT_TO_DOUBLE: + case Opcodes.LONG_TO_INT: + case Opcodes.LONG_TO_FLOAT: + case Opcodes.LONG_TO_DOUBLE: + case Opcodes.FLOAT_TO_INT: + case Opcodes.FLOAT_TO_LONG: + case Opcodes.FLOAT_TO_DOUBLE: + case Opcodes.DOUBLE_TO_INT: + case Opcodes.DOUBLE_TO_LONG: + case Opcodes.DOUBLE_TO_FLOAT: + case Opcodes.INT_TO_BYTE: + case Opcodes.INT_TO_CHAR: + case Opcodes.INT_TO_SHORT: + case Opcodes.ADD_INT_2ADDR: + case Opcodes.SUB_INT_2ADDR: + case Opcodes.MUL_INT_2ADDR: + case Opcodes.DIV_INT_2ADDR: + case Opcodes.REM_INT_2ADDR: + case Opcodes.AND_INT_2ADDR: + case Opcodes.OR_INT_2ADDR: + case Opcodes.XOR_INT_2ADDR: + case Opcodes.SHL_INT_2ADDR: + case Opcodes.SHR_INT_2ADDR: + case Opcodes.USHR_INT_2ADDR: + case Opcodes.ADD_LONG_2ADDR: + case Opcodes.SUB_LONG_2ADDR: + case Opcodes.MUL_LONG_2ADDR: + case Opcodes.DIV_LONG_2ADDR: + case Opcodes.REM_LONG_2ADDR: + case Opcodes.AND_LONG_2ADDR: + case Opcodes.OR_LONG_2ADDR: + case Opcodes.XOR_LONG_2ADDR: + case Opcodes.SHL_LONG_2ADDR: + case Opcodes.SHR_LONG_2ADDR: + case Opcodes.USHR_LONG_2ADDR: + case Opcodes.ADD_FLOAT_2ADDR: + case Opcodes.SUB_FLOAT_2ADDR: + case Opcodes.MUL_FLOAT_2ADDR: + case Opcodes.DIV_FLOAT_2ADDR: + case Opcodes.REM_FLOAT_2ADDR: + case Opcodes.ADD_DOUBLE_2ADDR: + case Opcodes.SUB_DOUBLE_2ADDR: + case Opcodes.MUL_DOUBLE_2ADDR: + case Opcodes.DIV_DOUBLE_2ADDR: + case Opcodes.REM_DOUBLE_2ADDR: { + short opcodeUnit = (short) opcode; + codeOut.write( + InstructionCodec.codeUnit( + opcodeUnit, + InstructionCodec.makeByte(a, b) + ) + ); + break; + } + case Opcodes.MOVE_FROM16: + case Opcodes.MOVE_WIDE_FROM16: + case Opcodes.MOVE_OBJECT_FROM16: { + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.getBUnit(b) + ); + break; + } + case Opcodes.ADD_INT_LIT8: + case Opcodes.RSUB_INT_LIT8: + case Opcodes.MUL_INT_LIT8: + case Opcodes.DIV_INT_LIT8: + case Opcodes.REM_INT_LIT8: + case Opcodes.AND_INT_LIT8: + case Opcodes.OR_INT_LIT8: + case Opcodes.XOR_INT_LIT8: + case Opcodes.SHL_INT_LIT8: + case Opcodes.SHR_INT_LIT8: + case Opcodes.USHR_INT_LIT8: { + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.codeUnit(b, InstructionCodec.getLiteralByte(literal)) + ); + break; + } + case Opcodes.IF_EQ: + case Opcodes.IF_NE: + case Opcodes.IF_LT: + case Opcodes.IF_GE: + case Opcodes.IF_GT: + case Opcodes.IF_LE: { + short relativeTarget = InstructionCodec.getTargetUnit(target, codeOut.cursor()); + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(a, b) + ), + relativeTarget + ); + break; + } + case Opcodes.ADD_INT_LIT16: + case Opcodes.RSUB_INT: + case Opcodes.MUL_INT_LIT16: + case Opcodes.DIV_INT_LIT16: + case Opcodes.REM_INT_LIT16: + case Opcodes.AND_INT_LIT16: + case Opcodes.OR_INT_LIT16: + case Opcodes.XOR_INT_LIT16: { + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(a, b) + ), + InstructionCodec.getLiteralUnit(literal) + ); + break; + } + case Opcodes.INSTANCE_OF: + case Opcodes.NEW_ARRAY: + case Opcodes.IGET: + case Opcodes.IGET_WIDE: + case Opcodes.IGET_OBJECT: + case Opcodes.IGET_BOOLEAN: + case Opcodes.IGET_BYTE: + case Opcodes.IGET_CHAR: + case Opcodes.IGET_SHORT: + case Opcodes.IPUT: + case Opcodes.IPUT_WIDE: + case Opcodes.IPUT_OBJECT: + case Opcodes.IPUT_BOOLEAN: + case Opcodes.IPUT_BYTE: + case Opcodes.IPUT_CHAR: + case Opcodes.IPUT_SHORT: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(a, b) + ), + indexUnit + ); + break; + } + case Opcodes.MOVE_16: + case Opcodes.MOVE_WIDE_16: + case Opcodes.MOVE_OBJECT_16: { + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit, InstructionCodec.getAUnit(a), InstructionCodec.getBUnit(b)); + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(0, 2) + ), + indexUnit, + InstructionCodec.codeUnit(a, b, 0, 0) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + switch (opcode) { + case Opcodes.CMPL_FLOAT: + case Opcodes.CMPG_FLOAT: + case Opcodes.CMPL_DOUBLE: + case Opcodes.CMPG_DOUBLE: + case Opcodes.CMP_LONG: + case Opcodes.AGET: + case Opcodes.AGET_WIDE: + case Opcodes.AGET_OBJECT: + case Opcodes.AGET_BOOLEAN: + case Opcodes.AGET_BYTE: + case Opcodes.AGET_CHAR: + case Opcodes.AGET_SHORT: + case Opcodes.APUT: + case Opcodes.APUT_WIDE: + case Opcodes.APUT_OBJECT: + case Opcodes.APUT_BOOLEAN: + case Opcodes.APUT_BYTE: + case Opcodes.APUT_CHAR: + case Opcodes.APUT_SHORT: + case Opcodes.ADD_INT: + case Opcodes.SUB_INT: + case Opcodes.MUL_INT: + case Opcodes.DIV_INT: + case Opcodes.REM_INT: + case Opcodes.AND_INT: + case Opcodes.OR_INT: + case Opcodes.XOR_INT: + case Opcodes.SHL_INT: + case Opcodes.SHR_INT: + case Opcodes.USHR_INT: + case Opcodes.ADD_LONG: + case Opcodes.SUB_LONG: + case Opcodes.MUL_LONG: + case Opcodes.DIV_LONG: + case Opcodes.REM_LONG: + case Opcodes.AND_LONG: + case Opcodes.OR_LONG: + case Opcodes.XOR_LONG: + case Opcodes.SHL_LONG: + case Opcodes.SHR_LONG: + case Opcodes.USHR_LONG: + case Opcodes.ADD_FLOAT: + case Opcodes.SUB_FLOAT: + case Opcodes.MUL_FLOAT: + case Opcodes.DIV_FLOAT: + case Opcodes.REM_FLOAT: + case Opcodes.ADD_DOUBLE: + case Opcodes.SUB_DOUBLE: + case Opcodes.MUL_DOUBLE: + case Opcodes.DIV_DOUBLE: + case Opcodes.REM_DOUBLE: { + codeOut.write( + InstructionCodec.codeUnit(opcode, a), + InstructionCodec.codeUnit(b, c) + ); + break; + } + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(0, 3) + ), + indexUnit, + InstructionCodec.codeUnit(a, b, c, 0) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(0, 4) + ), + indexUnit, + InstructionCodec.codeUnit(a, b, c, d) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY: + case Opcodes.INVOKE_VIRTUAL: + case Opcodes.INVOKE_SUPER: + case Opcodes.INVOKE_DIRECT: + case Opcodes.INVOKE_STATIC: + case Opcodes.INVOKE_INTERFACE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit( + opcode, + InstructionCodec.makeByte(e, 5) + ), + indexUnit, + InstructionCodec.codeUnit(a, b, c, d) + ); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + switch (opcode) { + case Opcodes.FILLED_NEW_ARRAY_RANGE: + case Opcodes.INVOKE_VIRTUAL_RANGE: + case Opcodes.INVOKE_SUPER_RANGE: + case Opcodes.INVOKE_DIRECT_RANGE: + case Opcodes.INVOKE_STATIC_RANGE: + case Opcodes.INVOKE_INTERFACE_RANGE: { + short indexUnit = (short) index; + codeOut.write( + InstructionCodec.codeUnit(opcode, registerCount), + indexUnit, + InstructionCodec.getAUnit(a)); + break; + } + default: { + throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); + } + } + } + + public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { + int baseAddress = codeOut.baseAddressForCursor(); + + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit); + codeOut.write(InstructionCodec.asUnsignedUnit(targets.length)); + + for (int key : keys) { + codeOut.writeInt(key); + } + + if (this.hasPromoter) { + for (int target : targets) { + target = this.insnPromoter.getPromotedAddress(target); + codeOut.writeInt(target - baseAddress); + } + } else { + for (int target : targets) { + codeOut.writeInt(target - baseAddress); + } + } + } + + public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { + int baseAddress = codeOut.baseAddressForCursor(); + + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit); + codeOut.write(InstructionCodec.asUnsignedUnit(targets.length)); + codeOut.writeInt(firstKey); + + if (this.hasPromoter) { + for (int target : targets) { + target = this.insnPromoter.getPromotedAddress(target); + codeOut.writeInt(target - baseAddress); + } + } else { + for (int target : targets) { + codeOut.writeInt(target - baseAddress); + } + } + } + + public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { + short opcodeUnit = (short) opcode; + codeOut.write(opcodeUnit); + + short elementWidthUnit = (short) elementWidth; + codeOut.write(elementWidthUnit); + + codeOut.writeInt(size); + + switch (elementWidth) { + case 1: { + codeOut.write((byte[]) data); + break; + } + case 2: { + codeOut.write((short[]) data); + break; + } + case 4: { + codeOut.write((int[]) data); + break; + } + case 8: { + codeOut.write((long[]) data); + break; + } + default: { + throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); + } + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/Opcodes.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/Opcodes.java new file mode 100644 index 00000000..99eb8845 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/Opcodes.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +/** + * All the Dalvik opcode value constants. See the related spec + * document for the meaning and instruction format of each opcode. + */ +public final class Opcodes { + /** + * pseudo-opcode used for nonstandard format payload "instructions". TODO: + * Retire this concept, and start treating the payload instructions + * more like the rest. + */ + public static final int SPECIAL_FORMAT = -1; + + // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + // END(opcodes) + + // TODO: Generate these payload opcodes with opcode-gen. + + /** + * special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int PACKED_SWITCH_PAYLOAD = 0x100; + + /** special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int SPARSE_SWITCH_PAYLOAD = 0x200; + + /** special pseudo-opcode value for fill-array-data data payload + * instructions + */ + public static final int FILL_ARRAY_DATA_PAYLOAD = 0x300; + + /** + * This class is uninstantiable. + */ + private Opcodes() { + // This space intentionally left blank. + } + + /** + * Gets the opcode out of an opcode unit, the latter of which may also + * include one or more argument values. + * + * @param opcodeUnit the opcode-containing code unit + * @return the extracted opcode + */ + public static int extractOpcodeFromUnit(int opcodeUnit) { + /* + * Note: This method bakes in knowledge that all opcodes are + * either single-byte or of the forms (byteValue << 8) or + * ((byteValue << 8) | 0xff). + */ + + int lowByte = opcodeUnit & 0xff; + return ((lowByte == 0) || (lowByte == 0xff)) ? opcodeUnit : lowByte; + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeInput.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeInput.java new file mode 100644 index 00000000..1f00ad14 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeInput.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +import java.io.EOFException; + +/** + * Reads code from a {@code short[]}. + */ +public final class ShortArrayCodeInput extends CodeCursor { + /** source array to read from */ + private final short[] array; + + /** + * Constructs an instance. + */ + public ShortArrayCodeInput(short[] array) { + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + } + + /** + * Returns whether there are any more code units to read. This + * is analogous to {@code hasNext()} on an interator. + */ + public boolean hasMore() { + return cursor() < array.length; + } + + /** + * Reads a code unit. + */ + public int read() throws EOFException { + try { + int value = array[cursor()]; + advance(1); + return value & 0xffff; + } catch (ArrayIndexOutOfBoundsException ex) { + throw new EOFException(); + } + } + + /** + * Reads two code units, treating them as a little-endian {@code int}. + */ + public int readInt() throws EOFException { + int short0 = read(); + int short1 = read(); + + return short0 | (short1 << 16); + } + + /** + * Reads four code units, treating them as a little-endian {@code long}. + */ + public long readLong() throws EOFException { + long short0 = read(); + long short1 = read(); + long short2 = read(); + long short3 = read(); + + return short0 | (short1 << 16) | (short2 << 32) | (short3 << 48); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeOutput.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeOutput.java new file mode 100644 index 00000000..e0cd2420 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeOutput.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.instruction; + +/** + * Writes code to a {@code short[]}. + */ +public final class ShortArrayCodeOutput extends CodeCursor { + /** array to write to */ + private short[] array; + + /** + * Constructs an instance. + * + * @param initSize the maximum number of code units that will be written + */ + public ShortArrayCodeOutput(int initSize) { + if (initSize < 0) { + throw new IllegalArgumentException("initSize < 0"); + } + + this.array = new short[initSize]; + } + + /** + * Constructs an instance by wrapping an exist array. + * @param array the array to write. + */ + public ShortArrayCodeOutput(short[] array) { + if (array == null) { + throw new IllegalArgumentException("array is null."); + } + this.array = array; + } + + /** + * Gets the array. The returned array contains exactly the data + * written (e.g. no leftover space at the end). + */ + public short[] getArray() { + int cursor = cursor(); + + if (cursor == array.length) { + return array; + } + + short[] result = new short[cursor]; + System.arraycopy(array, 0, result, 0, cursor); + return result; + } + + /** + * Writes a code unit. + */ + public void write(short codeUnit) { + ensureArrayLength(1); + array[cursor()] = codeUnit; + advance(1); + } + + /** + * Writes two code units. + */ + public void write(short u0, short u1) { + write(u0); + write(u1); + } + + /** + * Writes three code units. + */ + public void write(short u0, short u1, short u2) { + write(u0); + write(u1); + write(u2); + } + + /** + * Writes four code units. + */ + public void write(short u0, short u1, short u2, short u3) { + write(u0); + write(u1); + write(u2); + write(u3); + } + + /** + * Writes five code units. + */ + public void write(short u0, short u1, short u2, short u3, short u4) { + write(u0); + write(u1); + write(u2); + write(u3); + write(u4); + } + + /** + * Writes an {@code int}, little-endian. + */ + public void writeInt(int value) { + write((short) value); + write((short) (value >> 16)); + } + + /** + * Writes a {@code long}, little-endian. + */ + public void writeLong(long value) { + write((short) value); + write((short) (value >> 16)); + write((short) (value >> 32)); + write((short) (value >> 48)); + } + + /** + * Writes the contents of the given array. + */ + public void write(byte[] data) { + int value = 0; + boolean even = true; + for (byte b : data) { + if (even) { + value = b & 0xff; + even = false; + } else { + value |= b << 8; + write((short) value); + even = true; + } + } + + if (!even) { + write((short) value); + } + } + + /** + * Writes the contents of the given array. + */ + public void write(short[] data) { + for (short unit : data) { + write(unit); + } + } + + /** + * Writes the contents of the given array. + */ + public void write(int[] data) { + for (int i : data) { + writeInt(i); + } + } + + /** + * Writes the contents of the given array. + */ + public void write(long[] data) { + for (long l : data) { + writeLong(l); + } + } + + private void ensureArrayLength(int shortCountToWrite) { + int currPos = cursor(); + if (array.length - currPos < shortCountToWrite) { + short[] newArray = new short[array.length + (array.length >> 1)]; + System.arraycopy(array, 0, newArray, 0, currPos); + array = newArray; + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/Hex.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/Hex.java new file mode 100644 index 00000000..95d947fd --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/Hex.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.util; + +/** + * Utilities for formatting numbers as hexadecimal. + */ +public final class Hex { + /** + * This class is uninstantiable. + */ + private Hex() { + // This space intentionally left blank. + } + + /** + * Formats a {@code long} as an 8-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u8(long v) { + char[] result = new char[16]; + for (int i = 0; i < 16; i++) { + result[15 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u4(int v) { + char[] result = new char[8]; + for (int i = 0; i < 8; i++) { + result[7 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 3-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u3(int v) { + char[] result = new char[6]; + for (int i = 0; i < 6; i++) { + result[5 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2(int v) { + char[] result = new char[4]; + for (int i = 0; i < 4; i++) { + result[3 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as either a 2-byte unsigned hex value + * (if the value is small enough) or a 4-byte unsigned hex value (if + * not). + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2or4(int v) { + if (v == (char) v) { + return u2(v); + } else { + return u4(v); + } + } + + /** + * Formats an {@code int} as a 1-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u1(int v) { + char[] result = new char[2]; + for (int i = 0; i < 2; i++) { + result[1 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-bit unsigned hex nibble. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String uNibble(int v) { + char[] result = new char[1]; + + result[0] = Character.forDigit(v & 0x0f, 16); + return new String(result); + } + + /** + * Formats a {@code long} as an 8-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s8(long v) { + char[] result = new char[17]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 16; i++) { + result[16 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s4(int v) { + char[] result = new char[9]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 8; i++) { + result[8 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s2(int v) { + char[] result = new char[5]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 4; i++) { + result[4 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 1-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s1(int v) { + char[] result = new char[3]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 2; i++) { + result[2 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats a hex dump of a portion of a {@code byte[]}. The result + * is always newline-terminated, unless the passed-in length was zero, + * in which case the result is always the empty string ({@code ""}). + * + * @param arr {@code non-null;} array to format + * @param offset {@code >= 0;} offset to the part to dump + * @param length {@code >= 0;} number of bytes to dump + * @param outOffset {@code >= 0;} first output offset to print + * @param bpl {@code >= 0;} number of bytes of output per line + * @param addressLength {@code {2,4,6,8};} number of characters for each address + * header + * @return {@code non-null;} a string of the dump + */ + public static String dump(byte[] arr, int offset, int length, + int outOffset, int bpl, int addressLength) { + int end = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (end > arr.length)) { + throw new IndexOutOfBoundsException("arr.length " + + arr.length + "; " + + offset + "..!" + end); + } + + if (outOffset < 0) { + throw new IllegalArgumentException("outOffset < 0"); + } + + if (length == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(length * 4 + 6); + int col = 0; + + while (length > 0) { + if (col == 0) { + String astr; + switch (addressLength) { + case 2: astr = Hex.u1(outOffset); break; + case 4: astr = Hex.u2(outOffset); break; + case 6: astr = Hex.u3(outOffset); break; + default: astr = Hex.u4(outOffset); break; + } + sb.append(astr); + sb.append(": "); + } else if ((col & 1) == 0) { + sb.append(' '); + } + sb.append(Hex.u1(arr[offset])); + outOffset++; + offset++; + col++; + if (col == bpl) { + sb.append('\n'); + col = 0; + } + length--; + } + + if (col != 0) { + sb.append('\n'); + } + + return sb.toString(); + } + + public static String toHexString(byte[] ubytes) { + StringBuilder strBuilder = new StringBuilder(ubytes.length << 1); + for (byte b : ubytes) { + strBuilder.append(u1(b)); + } + return strBuilder.toString(); + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/IndexMap.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/IndexMap.java new file mode 100644 index 00000000..a9e46362 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/IndexMap.java @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.util; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.EncodedValueCodec; +import com.tencent.tinker.android.dex.EncodedValueReader; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.Leb128; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dex.util.ByteOutput; +import com.tencent.tinker.android.utils.SparseIntArray; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.BitSet; + +/** + * Created by tangyinsheng on 2016/6/29. + */ +public class IndexMap { + private final SparseIntArray stringIdsMap = new SparseIntArray(); + private final SparseIntArray typeIdsMap = new SparseIntArray(); + private final SparseIntArray protoIdsMap = new SparseIntArray(); + private final SparseIntArray fieldIdsMap = new SparseIntArray(); + private final SparseIntArray methodIdsMap = new SparseIntArray(); + private final SparseIntArray typeListOffsetsMap = new SparseIntArray(); + private final SparseIntArray annotationOffsetsMap = new SparseIntArray(); + private final SparseIntArray annotationSetOffsetsMap = new SparseIntArray(); + private final SparseIntArray annotationSetRefListOffsetsMap = new SparseIntArray(); + private final SparseIntArray annotationsDirectoryOffsetsMap = new SparseIntArray(); + private final SparseIntArray staticValuesOffsetsMap = new SparseIntArray(); + private final SparseIntArray classDataOffsetsMap = new SparseIntArray(); + private final SparseIntArray debugInfoItemOffsetsMap = new SparseIntArray(); + private final SparseIntArray codeOffsetsMap = new SparseIntArray(); + + private final BitSet deletedStringIds = new BitSet(); + private final BitSet deletedTypeIds = new BitSet(); + private final BitSet deletedProtoIds = new BitSet(); + private final BitSet deletedFieldIds = new BitSet(); + private final BitSet deletedMethodIds = new BitSet(); + private final BitSet deletedTypeListOffsets = new BitSet(); + private final BitSet deletedAnnotationOffsets = new BitSet(); + private final BitSet deletedAnnotationSetOffsets = new BitSet(); + private final BitSet deletedAnnotationSetRefListOffsets = new BitSet(); + private final BitSet deletedAnnotationsDirectoryOffsets = new BitSet(); + private final BitSet deletedStaticValuesOffsets = new BitSet(); + private final BitSet deletedClassDataOffsets = new BitSet(); + private final BitSet deletedDebugInfoItemOffsets = new BitSet(); + private final BitSet deletedCodeOffsets = new BitSet(); + + public void mapStringIds(int oldIndex, int newIndex) { + stringIdsMap.put(oldIndex, newIndex); + } + + public void markStringIdDeleted(int index) { + if (index < 0) return; + deletedStringIds.set(index); + } + + public void mapTypeIds(int oldIndex, int newIndex) { + typeIdsMap.put(oldIndex, newIndex); + } + + public void markTypeIdDeleted(int index) { + if (index < 0) return; + deletedTypeIds.set(index); + } + + public void mapProtoIds(int oldIndex, int newIndex) { + protoIdsMap.put(oldIndex, newIndex); + } + + public void markProtoIdDeleted(int index) { + if (index < 0) return; + deletedProtoIds.set(index); + } + + public void mapFieldIds(int oldIndex, int newIndex) { + fieldIdsMap.put(oldIndex, newIndex); + } + + public void markFieldIdDeleted(int index) { + if (index < 0) return; + deletedFieldIds.set(index); + } + + public void mapMethodIds(int oldIndex, int newIndex) { + methodIdsMap.put(oldIndex, newIndex); + } + + public void markMethodIdDeleted(int index) { + if (index < 0) return; + deletedMethodIds.set(index); + } + + public void mapTypeListOffset(int oldOffset, int newOffset) { + typeListOffsetsMap.put(oldOffset, newOffset); + } + + public void markTypeListDeleted(int offset) { + if (offset < 0) return; + deletedTypeListOffsets.set(offset); + } + + public void mapAnnotationOffset(int oldOffset, int newOffset) { + annotationOffsetsMap.put(oldOffset, newOffset); + } + + public void markAnnotationDeleted(int offset) { + if (offset < 0) return; + deletedAnnotationOffsets.set(offset); + } + + public void mapAnnotationSetOffset(int oldOffset, int newOffset) { + annotationSetOffsetsMap.put(oldOffset, newOffset); + } + + public void markAnnotationSetDeleted(int offset) { + if (offset < 0) return; + deletedAnnotationSetOffsets.set(offset); + } + + public void mapAnnotationSetRefListOffset(int oldOffset, int newOffset) { + annotationSetRefListOffsetsMap.put(oldOffset, newOffset); + } + + public void markAnnotationSetRefListDeleted(int offset) { + if (offset < 0) return; + deletedAnnotationSetRefListOffsets.set(offset); + } + + public void mapAnnotationsDirectoryOffset(int oldOffset, int newOffset) { + annotationsDirectoryOffsetsMap.put(oldOffset, newOffset); + } + + public void markAnnotationsDirectoryDeleted(int offset) { + if (offset < 0) return; + deletedAnnotationsDirectoryOffsets.set(offset); + } + + public void mapStaticValuesOffset(int oldOffset, int newOffset) { + staticValuesOffsetsMap.put(oldOffset, newOffset); + } + + public void markStaticValuesDeleted(int offset) { + if (offset < 0) return; + deletedStaticValuesOffsets.set(offset); + } + + public void mapClassDataOffset(int oldOffset, int newOffset) { + classDataOffsetsMap.put(oldOffset, newOffset); + } + + public void markClassDataDeleted(int offset) { + if (offset < 0) return; + deletedClassDataOffsets.set(offset); + } + + public void mapDebugInfoItemOffset(int oldOffset, int newOffset) { + debugInfoItemOffsetsMap.put(oldOffset, newOffset); + } + + public void markDebugInfoItemDeleted(int offset) { + if (offset < 0) return; + deletedDebugInfoItemOffsets.set(offset); + } + + public void mapCodeOffset(int oldOffset, int newOffset) { + codeOffsetsMap.put(oldOffset, newOffset); + } + + public void markCodeDeleted(int offset) { + if (offset < 0) return; + deletedCodeOffsets.set(offset); + } + + public int adjustStringIndex(int stringIndex) { + int index = stringIdsMap.indexOfKey(stringIndex); + if (index < 0) { + return (stringIndex >= 0 && deletedStringIds.get(stringIndex) ? -1 : stringIndex); + } else { + return stringIdsMap.valueAt(index); + } + } + + public int adjustTypeIdIndex(int typeIdIndex) { + int index = typeIdsMap.indexOfKey(typeIdIndex); + if (index < 0) { + return (typeIdIndex >= 0 && deletedTypeIds.get(typeIdIndex) ? -1 : typeIdIndex); + } else { + return typeIdsMap.valueAt(index); + } + } + + public int adjustProtoIdIndex(int protoIndex) { + int index = protoIdsMap.indexOfKey(protoIndex); + if (index < 0) { + return (protoIndex >= 0 && deletedProtoIds.get(protoIndex) ? -1 : protoIndex); + } else { + return protoIdsMap.valueAt(index); + } + } + + public int adjustFieldIdIndex(int fieldIndex) { + int index = fieldIdsMap.indexOfKey(fieldIndex); + if (index < 0) { + return (fieldIndex >= 0 && deletedFieldIds.get(fieldIndex) ? -1 : fieldIndex); + } else { + return fieldIdsMap.valueAt(index); + } + } + + public int adjustMethodIdIndex(int methodIndex) { + int index = methodIdsMap.indexOfKey(methodIndex); + if (index < 0) { + return (methodIndex >= 0 && deletedMethodIds.get(methodIndex) ? -1 : methodIndex); + } else { + return methodIdsMap.valueAt(index); + } + } + + public int adjustTypeListOffset(int typeListOffset) { + int index = typeListOffsetsMap.indexOfKey(typeListOffset); + if (index < 0) { + return (typeListOffset >= 0 && deletedTypeListOffsets.get(typeListOffset) ? -1 : typeListOffset); + } else { + return typeListOffsetsMap.valueAt(index); + } + } + + public int adjustAnnotationOffset(int annotationOffset) { + int index = annotationOffsetsMap.indexOfKey(annotationOffset); + if (index < 0) { + return (annotationOffset >= 0 && deletedAnnotationOffsets.get(annotationOffset) ? -1 : annotationOffset); + } else { + return annotationOffsetsMap.valueAt(index); + } + } + + public int adjustAnnotationSetOffset(int annotationSetOffset) { + int index = annotationSetOffsetsMap.indexOfKey(annotationSetOffset); + if (index < 0) { + return (annotationSetOffset >= 0 && deletedAnnotationSetOffsets.get(annotationSetOffset) ? -1 : annotationSetOffset); + } else { + return annotationSetOffsetsMap.valueAt(index); + } + } + + public int adjustAnnotationSetRefListOffset(int annotationSetRefListOffset) { + int index = annotationSetRefListOffsetsMap.indexOfKey(annotationSetRefListOffset); + if (index < 0) { + return (annotationSetRefListOffset >= 0 && deletedAnnotationSetRefListOffsets.get(annotationSetRefListOffset) ? -1 : annotationSetRefListOffset); + } else { + return annotationSetRefListOffsetsMap.valueAt(index); + } + } + + public int adjustAnnotationsDirectoryOffset(int annotationsDirectoryOffset) { + int index = annotationsDirectoryOffsetsMap.indexOfKey(annotationsDirectoryOffset); + if (index < 0) { + return (annotationsDirectoryOffset >= 0 && deletedAnnotationsDirectoryOffsets.get(annotationsDirectoryOffset) ? -1 : annotationsDirectoryOffset); + } else { + return annotationsDirectoryOffsetsMap.valueAt(index); + } + } + + public int adjustStaticValuesOffset(int staticValuesOffset) { + int index = staticValuesOffsetsMap.indexOfKey(staticValuesOffset); + if (index < 0) { + return (staticValuesOffset >= 0 && deletedStaticValuesOffsets.get(staticValuesOffset) ? -1 : staticValuesOffset); + } else { + return staticValuesOffsetsMap.valueAt(index); + } + } + + public int adjustClassDataOffset(int classDataOffset) { + int index = classDataOffsetsMap.indexOfKey(classDataOffset); + if (index < 0) { + return (classDataOffset >= 0 && deletedClassDataOffsets.get(classDataOffset) ? -1 : classDataOffset); + } else { + return classDataOffsetsMap.valueAt(index); + } + } + + public int adjustDebugInfoItemOffset(int debugInfoItemOffset) { + int index = debugInfoItemOffsetsMap.indexOfKey(debugInfoItemOffset); + if (index < 0) { + return (debugInfoItemOffset >= 0 && deletedDebugInfoItemOffsets.get(debugInfoItemOffset) ? -1 : debugInfoItemOffset); + } else { + return debugInfoItemOffsetsMap.valueAt(index); + } + } + + public int adjustCodeOffset(int codeOffset) { + int index = codeOffsetsMap.indexOfKey(codeOffset); + if (index < 0) { + return (codeOffset >= 0 && deletedCodeOffsets.get(codeOffset) ? -1 : codeOffset); + } else { + return codeOffsetsMap.valueAt(index); + } + } + + public TypeList adjust(TypeList typeList) { + if (typeList == TypeList.EMPTY) { + return typeList; + } + short[] types = new short[typeList.types.length]; + for (int i = 0; i < types.length; ++i) { + types[i] = (short) adjustTypeIdIndex(typeList.types[i]); + } + return new TypeList(typeList.off, types); + } + + public MethodId adjust(MethodId methodId) { + int adjustedDeclaringClassIndex = adjustTypeIdIndex(methodId.declaringClassIndex); + int adjustedProtoIndex = adjustProtoIdIndex(methodId.protoIndex); + int adjustedNameIndex = adjustStringIndex(methodId.nameIndex); + return new MethodId( + methodId.off, adjustedDeclaringClassIndex, adjustedProtoIndex, adjustedNameIndex + ); + } + + public FieldId adjust(FieldId fieldId) { + int adjustedDeclaringClassIndex = adjustTypeIdIndex(fieldId.declaringClassIndex); + int adjustedTypeIndex = adjustTypeIdIndex(fieldId.typeIndex); + int adjustedNameIndex = adjustStringIndex(fieldId.nameIndex); + return new FieldId( + fieldId.off, adjustedDeclaringClassIndex, adjustedTypeIndex, adjustedNameIndex + ); + } + + public ProtoId adjust(ProtoId protoId) { + int adjustedShortyIndex = adjustStringIndex(protoId.shortyIndex); + int adjustedReturnTypeIndex = adjustTypeIdIndex(protoId.returnTypeIndex); + int adjustedParametersOffset = adjustTypeListOffset(protoId.parametersOffset); + return new ProtoId( + protoId.off, adjustedShortyIndex, adjustedReturnTypeIndex, adjustedParametersOffset + ); + } + + public ClassDef adjust(ClassDef classDef) { + int adjustedTypeIndex = adjustTypeIdIndex(classDef.typeIndex); + int adjustedSupertypeIndex = adjustTypeIdIndex(classDef.supertypeIndex); + int adjustedInterfacesOffset = adjustTypeListOffset(classDef.interfacesOffset); + int adjustedSourceFileIndex = adjustStringIndex(classDef.sourceFileIndex); + int adjustedAnnotationsOffset = adjustAnnotationsDirectoryOffset(classDef.annotationsOffset); + int adjustedClassDataOffset = adjustClassDataOffset(classDef.classDataOffset); + int adjustedStaticValuesOffset = adjustStaticValuesOffset(classDef.staticValuesOffset); + return new ClassDef( + classDef.off, adjustedTypeIndex, classDef.accessFlags, adjustedSupertypeIndex, + adjustedInterfacesOffset, adjustedSourceFileIndex, adjustedAnnotationsOffset, + adjustedClassDataOffset, adjustedStaticValuesOffset + ); + } + + public ClassData adjust(ClassData classData) { + ClassData.Field[] adjustedStaticFields = adjustFields(classData.staticFields); + ClassData.Field[] adjustedInstanceFields = adjustFields(classData.instanceFields); + ClassData.Method[] adjustedDirectMethods = adjustMethods(classData.directMethods); + ClassData.Method[] adjustedVirtualMethods = adjustMethods(classData.virtualMethods); + return new ClassData( + classData.off, adjustedStaticFields, adjustedInstanceFields, + adjustedDirectMethods, adjustedVirtualMethods + ); + } + + public Code adjust(Code code) { + int adjustedDebugInfoOffset = adjustDebugInfoItemOffset(code.debugInfoOffset); + short[] adjustedInstructions = adjustInstructions(code.instructions); + Code.CatchHandler[] adjustedCatchHandlers = adjustCatchHandlers(code.catchHandlers); + return new Code( + code.off, code.registersSize, code.insSize, code.outsSize, + adjustedDebugInfoOffset, adjustedInstructions, code.tries, adjustedCatchHandlers + ); + } + + private short[] adjustInstructions(short[] instructions) { + if (instructions == null || instructions.length == 0) { + return instructions; + } + InstructionTransformer insTrans = new InstructionTransformer(this); + return insTrans.transform(instructions); + } + + private Code.CatchHandler[] adjustCatchHandlers(Code.CatchHandler[] catchHandlers) { + if (catchHandlers == null || catchHandlers.length == 0) { + return catchHandlers; + } + Code.CatchHandler[] adjustedCatchHandlers = new Code.CatchHandler[catchHandlers.length]; + for (int i = 0; i < catchHandlers.length; ++i) { + Code.CatchHandler catchHandler = catchHandlers[i]; + int typeIndexesCount = catchHandler.typeIndexes.length; + int[] adjustedTypeIndexes = new int[typeIndexesCount]; + for (int j = 0; j < typeIndexesCount; ++j) { + adjustedTypeIndexes[j] = adjustTypeIdIndex(catchHandler.typeIndexes[j]); + } + adjustedCatchHandlers[i] = new Code.CatchHandler( + adjustedTypeIndexes, catchHandler.addresses, + catchHandler.catchAllAddress, catchHandler.offset + ); + } + return adjustedCatchHandlers; + } + + private ClassData.Field[] adjustFields(ClassData.Field[] fields) { + ClassData.Field[] adjustedFields = new ClassData.Field[fields.length]; + for (int i = 0; i < fields.length; ++i) { + ClassData.Field field = fields[i]; + int adjustedFieldIndex = adjustFieldIdIndex(field.fieldIndex); + adjustedFields[i] = new ClassData.Field(adjustedFieldIndex, field.accessFlags); + } + return adjustedFields; + } + + private ClassData.Method[] adjustMethods(ClassData.Method[] methods) { + ClassData.Method[] adjustedMethods = new ClassData.Method[methods.length]; + for (int i = 0; i < methods.length; ++i) { + ClassData.Method method = methods[i]; + int adjustedMethodIndex = adjustMethodIdIndex(method.methodIndex); + int adjustedCodeOffset = adjustCodeOffset(method.codeOffset); + adjustedMethods[i] = new ClassData.Method( + adjustedMethodIndex, method.accessFlags, adjustedCodeOffset + ); + } + return adjustedMethods; + } + + public DebugInfoItem adjust(DebugInfoItem debugInfoItem) { + int[] parameterNames = adjustParameterNames(debugInfoItem.parameterNames); + byte[] infoSTM = adjustDebugInfoItemSTM(debugInfoItem.infoSTM); + return new DebugInfoItem( + debugInfoItem.off, debugInfoItem.lineStart, parameterNames, infoSTM + ); + } + + private int[] adjustParameterNames(int[] parameterNames) { + int size = parameterNames.length; + int[] adjustedParameterNames = new int[size]; + for (int i = 0; i < size; ++i) { + adjustedParameterNames[i] = adjustStringIndex(parameterNames[i]); + } + return adjustedParameterNames; + } + + private byte[] adjustDebugInfoItemSTM(byte[] infoSTM) { + ByteArrayInputStream bais = new ByteArrayInputStream(infoSTM); + final ByteArrayInputStream baisRef = bais; + ByteInput inAdapter = new ByteInput() { + @Override + public byte readByte() { + return (byte) (baisRef.read() & 0xFF); + } + }; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(infoSTM.length + 512); + final ByteArrayOutputStream baosRef = baos; + ByteOutput outAdapter = new ByteOutput() { + @Override + public void writeByte(int i) { + baosRef.write(i); + } + }; + + outside_whileloop: + while (true) { + int opcode = bais.read() & 0xFF; + baos.write(opcode); + switch (opcode) { + case DebugInfoItem.DBG_END_SEQUENCE: { + break outside_whileloop; + } + case DebugInfoItem.DBG_ADVANCE_PC: { + int addrDiff = Leb128.readUnsignedLeb128(inAdapter); + Leb128.writeUnsignedLeb128(outAdapter, addrDiff); + break; + } + case DebugInfoItem.DBG_ADVANCE_LINE: { + int lineDiff = Leb128.readSignedLeb128(inAdapter); + Leb128.writeSignedLeb128(outAdapter, lineDiff); + break; + } + case DebugInfoItem.DBG_START_LOCAL: + case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { + int registerNum = Leb128.readUnsignedLeb128(inAdapter); + Leb128.writeUnsignedLeb128(outAdapter, registerNum); + + int nameIndex = adjustStringIndex(Leb128.readUnsignedLeb128p1(inAdapter)); + Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); + + int typeIndex = adjustTypeIdIndex(Leb128.readUnsignedLeb128p1(inAdapter)); + Leb128.writeUnsignedLeb128p1(outAdapter, typeIndex); + + if (opcode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { + int sigIndex = adjustStringIndex(Leb128.readUnsignedLeb128p1(inAdapter)); + Leb128.writeUnsignedLeb128p1(outAdapter, sigIndex); + } + break; + } + case DebugInfoItem.DBG_END_LOCAL: + case DebugInfoItem.DBG_RESTART_LOCAL: { + int registerNum = Leb128.readUnsignedLeb128(inAdapter); + Leb128.writeUnsignedLeb128(outAdapter, registerNum); + break; + } + case DebugInfoItem.DBG_SET_FILE: { + int nameIndex = adjustStringIndex(Leb128.readUnsignedLeb128p1(inAdapter)); + Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); + break; + } + case DebugInfoItem.DBG_SET_PROLOGUE_END: + case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: + default: { + break; + } + } + } + + return baos.toByteArray(); + } + + public EncodedValue adjust(EncodedValue encodedArray) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(encodedArray.data.length); + new EncodedValueTransformer( + new ByteOutput() { + @Override + public void writeByte(int i) { + baos.write(i); + } + } + ).transformArray( + new EncodedValueReader(encodedArray, EncodedValueReader.ENCODED_ARRAY) + ); + return new EncodedValue(encodedArray.off, baos.toByteArray()); + } + + public Annotation adjust(Annotation annotation) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(annotation.encodedAnnotation.data.length); + new EncodedValueTransformer( + new ByteOutput() { + @Override + public void writeByte(int i) { + baos.write(i); + } + } + ).transformAnnotation(annotation.getReader()); + return new Annotation( + annotation.off, + annotation.visibility, + new EncodedValue(annotation.encodedAnnotation.off, baos.toByteArray()) + ); + } + + public AnnotationSet adjust(AnnotationSet annotationSet) { + int size = annotationSet.annotationOffsets.length; + int[] adjustedAnnotationOffsets = new int[size]; + for (int i = 0; i < size; ++i) { + adjustedAnnotationOffsets[i] + = adjustAnnotationOffset(annotationSet.annotationOffsets[i]); + } + return new AnnotationSet(annotationSet.off, adjustedAnnotationOffsets); + } + + public AnnotationSetRefList adjust(AnnotationSetRefList annotationSetRefList) { + int size = annotationSetRefList.annotationSetRefItems.length; + int[] adjustedAnnotationSetRefItems = new int[size]; + for (int i = 0; i < size; ++i) { + adjustedAnnotationSetRefItems[i] + = adjustAnnotationSetOffset(annotationSetRefList.annotationSetRefItems[i]); + } + return new AnnotationSetRefList(annotationSetRefList.off, adjustedAnnotationSetRefItems); + } + + public AnnotationsDirectory adjust(AnnotationsDirectory annotationsDirectory) { + int adjustedClassAnnotationsOffset + = adjustAnnotationSetOffset(annotationsDirectory.classAnnotationsOffset); + + int[][] adjustedFieldAnnotations + = new int[annotationsDirectory.fieldAnnotations.length][2]; + for (int i = 0; i < adjustedFieldAnnotations.length; ++i) { + adjustedFieldAnnotations[i][0] + = adjustFieldIdIndex(annotationsDirectory.fieldAnnotations[i][0]); + adjustedFieldAnnotations[i][1] + = adjustAnnotationSetOffset(annotationsDirectory.fieldAnnotations[i][1]); + } + + int[][] adjustedMethodAnnotations + = new int[annotationsDirectory.methodAnnotations.length][2]; + for (int i = 0; i < adjustedMethodAnnotations.length; ++i) { + adjustedMethodAnnotations[i][0] + = adjustMethodIdIndex(annotationsDirectory.methodAnnotations[i][0]); + adjustedMethodAnnotations[i][1] + = adjustAnnotationSetOffset(annotationsDirectory.methodAnnotations[i][1]); + } + + int[][] adjustedParameterAnnotations + = new int[annotationsDirectory.parameterAnnotations.length][2]; + for (int i = 0; i < adjustedParameterAnnotations.length; ++i) { + adjustedParameterAnnotations[i][0] + = adjustMethodIdIndex(annotationsDirectory.parameterAnnotations[i][0]); + adjustedParameterAnnotations[i][1] + = adjustAnnotationSetRefListOffset( + annotationsDirectory.parameterAnnotations[i][1] + ); + } + + return new AnnotationsDirectory( + annotationsDirectory.off, adjustedClassAnnotationsOffset, + adjustedFieldAnnotations, adjustedMethodAnnotations, adjustedParameterAnnotations + ); + } + + /** + * Adjust an encoded value or array. + */ + private final class EncodedValueTransformer { + private final ByteOutput out; + + EncodedValueTransformer(ByteOutput out) { + this.out = out; + } + + public void transform(EncodedValueReader reader) { + switch (reader.peek()) { + case EncodedValueReader.ENCODED_BYTE: + EncodedValueCodec.writeSignedIntegralValue(out, EncodedValueReader.ENCODED_BYTE, reader.readByte()); + break; + case EncodedValueReader.ENCODED_SHORT: + EncodedValueCodec.writeSignedIntegralValue(out, EncodedValueReader.ENCODED_SHORT, reader.readShort()); + break; + case EncodedValueReader.ENCODED_INT: + EncodedValueCodec.writeSignedIntegralValue(out, EncodedValueReader.ENCODED_INT, reader.readInt()); + break; + case EncodedValueReader.ENCODED_LONG: + EncodedValueCodec.writeSignedIntegralValue(out, EncodedValueReader.ENCODED_LONG, reader.readLong()); + break; + case EncodedValueReader.ENCODED_CHAR: + EncodedValueCodec.writeUnsignedIntegralValue(out, EncodedValueReader.ENCODED_CHAR, reader.readChar()); + break; + case EncodedValueReader.ENCODED_FLOAT: + // Shift value left 32 so that right-zero-extension works. + long longBits = ((long) Float.floatToIntBits(reader.readFloat())) << 32; + EncodedValueCodec.writeRightZeroExtendedValue(out, EncodedValueReader.ENCODED_FLOAT, longBits); + break; + case EncodedValueReader.ENCODED_DOUBLE: + EncodedValueCodec.writeRightZeroExtendedValue( + out, EncodedValueReader.ENCODED_DOUBLE, Double.doubleToLongBits(reader.readDouble())); + break; + case EncodedValueReader.ENCODED_STRING: + EncodedValueCodec.writeUnsignedIntegralValue( + out, EncodedValueReader.ENCODED_STRING, adjustStringIndex(reader.readString())); + break; + case EncodedValueReader.ENCODED_TYPE: + EncodedValueCodec.writeUnsignedIntegralValue( + out, EncodedValueReader.ENCODED_TYPE, adjustTypeIdIndex(reader.readType())); + break; + case EncodedValueReader.ENCODED_FIELD: + EncodedValueCodec.writeUnsignedIntegralValue( + out, EncodedValueReader.ENCODED_FIELD, adjustFieldIdIndex(reader.readField())); + break; + case EncodedValueReader.ENCODED_ENUM: + EncodedValueCodec.writeUnsignedIntegralValue( + out, EncodedValueReader.ENCODED_ENUM, adjustFieldIdIndex(reader.readEnum())); + break; + case EncodedValueReader.ENCODED_METHOD: + EncodedValueCodec.writeUnsignedIntegralValue( + out, EncodedValueReader.ENCODED_METHOD, adjustMethodIdIndex(reader.readMethod())); + break; + case EncodedValueReader.ENCODED_ARRAY: + writeTypeAndArg(EncodedValueReader.ENCODED_ARRAY, 0); + transformArray(reader); + break; + case EncodedValueReader.ENCODED_ANNOTATION: + writeTypeAndArg(EncodedValueReader.ENCODED_ANNOTATION, 0); + transformAnnotation(reader); + break; + case EncodedValueReader.ENCODED_NULL: + reader.readNull(); + writeTypeAndArg(EncodedValueReader.ENCODED_NULL, 0); + break; + case EncodedValueReader.ENCODED_BOOLEAN: + boolean value = reader.readBoolean(); + writeTypeAndArg(EncodedValueReader.ENCODED_BOOLEAN, value ? 1 : 0); + break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(reader.peek())); + } + } + + private void transformAnnotation(EncodedValueReader reader) { + int fieldCount = reader.readAnnotation(); + Leb128.writeUnsignedLeb128(out, adjustTypeIdIndex(reader.getAnnotationType())); + Leb128.writeUnsignedLeb128(out, fieldCount); + for (int i = 0; i < fieldCount; i++) { + Leb128.writeUnsignedLeb128(out, adjustStringIndex(reader.readAnnotationName())); + transform(reader); + } + } + + private void transformArray(EncodedValueReader reader) { + int size = reader.readArray(); + Leb128.writeUnsignedLeb128(out, size); + for (int i = 0; i < size; i++) { + transform(reader); + } + } + + private void writeTypeAndArg(int type, int arg) { + out.writeByte((arg << 5) | type); + } + } +} diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/InstructionTransformer.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/InstructionTransformer.java new file mode 100644 index 00000000..cc6562d2 --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/InstructionTransformer.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.dx.util; + +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dx.instruction.InstructionCodec; +import com.tencent.tinker.android.dx.instruction.InstructionPromoter; +import com.tencent.tinker.android.dx.instruction.InstructionReader; +import com.tencent.tinker.android.dx.instruction.InstructionVisitor; +import com.tencent.tinker.android.dx.instruction.InstructionWriter; +import com.tencent.tinker.android.dx.instruction.ShortArrayCodeInput; +import com.tencent.tinker.android.dx.instruction.ShortArrayCodeOutput; + +import java.io.EOFException; + +/** + * Created by tangyinsheng on 2016/6/29. + */ +public final class InstructionTransformer { + private final com.tencent.tinker.android.dx.util.IndexMap indexMap; + + public InstructionTransformer(com.tencent.tinker.android.dx.util.IndexMap indexMap) { + this.indexMap = indexMap; + } + + public short[] transform(short[] encodedInstructions) throws DexException { + ShortArrayCodeOutput out = new ShortArrayCodeOutput(encodedInstructions.length); + InstructionPromoter ipmo = new InstructionPromoter(); + InstructionWriter iw = new InstructionWriter(out, ipmo); + InstructionReader ir = new InstructionReader(new ShortArrayCodeInput(encodedInstructions)); + + try { + // First visit, we collect mappings from original target address to promoted target address. + ir.accept(new InstructionTransformVisitor(ipmo)); + + // Then do the real transformation work. + ir.accept(new InstructionTransformVisitor(iw)); + } catch (EOFException e) { + throw new DexException(e); + } + + return out.getArray(); + } + + private final class InstructionTransformVisitor extends InstructionVisitor { + InstructionTransformVisitor(InstructionVisitor iv) { + super(iv); + } + + @Override + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitZeroRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal); + } + + @Override + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitOneRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a); + } + + @Override + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitTwoRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a, b); + } + + @Override + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitThreeRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a, b, c); + } + + @Override + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitFourRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a, b, c, d); + } + + @Override + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitFiveRegisterInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a, b, c, d, e); + } + + @Override + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + int mappedIndex = transformIndexIfNeeded(index, indexType); + super.visitRegisterRangeInsn(currentAddress, opcode, mappedIndex, indexType, target, literal, a, registerCount); + } + + private int transformIndexIfNeeded(int index, int indexType) { + switch (indexType) { + case InstructionCodec.INDEX_TYPE_STRING_REF: { + return indexMap.adjustStringIndex(index); + } + case InstructionCodec.INDEX_TYPE_TYPE_REF: { + return indexMap.adjustTypeIdIndex(index); + } + case InstructionCodec.INDEX_TYPE_FIELD_REF: { + return indexMap.adjustFieldIdIndex(index); + } + case InstructionCodec.INDEX_TYPE_METHOD_REF: { + return indexMap.adjustMethodIdIndex(index); + } + default: { + return index; + } + } + } + + } +} + diff --git a/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/utils/SparseIntArray.java b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/utils/SparseIntArray.java new file mode 100644 index 00000000..20bfbf4e --- /dev/null +++ b/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/utils/SparseIntArray.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.android.utils; + +/** + * SparseIntArrays map integers to integers. Unlike a normal array of integers, + * there can be gaps in the indices. It is intended to be more memory efficient + * than using a HashMap to map Integers to Integers, both because it avoids + * auto-boxing keys and values and its data structure doesn't rely on an extra entry object + * for each mapping. + * + *

Note that this container keeps its mappings in an array data structure, + * using a binary search to find keys. The implementation is not intended to be appropriate for + * data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashMap, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%.

+ * + *

It is possible to iterate over the items in this container using + * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using + * keyAt(int) with ascending values of the index will return the + * keys in ascending order, or the values corresponding to the keys in ascending + * order in the case of valueAt(int).

+ */ +public class SparseIntArray implements Cloneable { + private static final int[] EMPTY_INT_ARRAY = new int[0]; + private int[] mKeys; + private int[] mValues; + private int mSize; + + /** + * Creates a new SparseIntArray containing no mappings. + */ + public SparseIntArray() { + this(10); + } + + /** + * Creates a new SparseIntArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public SparseIntArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = SparseIntArray.EMPTY_INT_ARRAY; + mValues = SparseIntArray.EMPTY_INT_ARRAY; + } else { + mKeys = new int[initialCapacity]; + mValues = new int[mKeys.length]; + } + mSize = 0; + } + + /** + * Given the current size of an array, returns an ideal size to which the array should grow. + * This is typically double the given size, but should not be relied upon to do so in the + * future. + */ + public static int growSize(int currentSize) { + return currentSize <= 4 ? 8 : currentSize + (currentSize >> 1); + } + + @Override + public SparseIntArray clone() { + SparseIntArray clone = null; + try { + clone = (SparseIntArray) super.clone(); + clone.mKeys = mKeys.clone(); + clone.mValues = mValues.clone(); + } catch (CloneNotSupportedException cnse) { + /* ignore */ + } + return clone; + } + + /** + * Gets the int mapped from the specified key, or 0 + * if no such mapping has been made. + */ + public int get(int key) { + return get(key, 0); + } + + /** + * Gets the int mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public int get(int key, int valueIfKeyNotFound) { + int i = binarySearch(mKeys, mSize, key); + + if (i < 0) { + return valueIfKeyNotFound; + } else { + return mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) { + int i = binarySearch(mKeys, mSize, key); + + if (i >= 0) { + removeAt(i); + } + } + + /** + * Removes the mapping at the given index. + */ + public void removeAt(int index) { + System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); + System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1)); + --mSize; + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, int value) { + int i = binarySearch(mKeys, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + mKeys = insertElementIntoIntArray(mKeys, mSize, i, key); + mValues = insertElementIntoIntArray(mValues, mSize, i, value); + ++mSize; + } + } + + /** + * Returns the number of key-value mappings that this SparseIntArray + * currently stores. + */ + public int size() { + return mSize; + } + + /** + * Given an index in the range 0...size()-1, returns + * the key from the indexth key-value mapping that this + * SparseIntArray stores. + * + *

The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., keyAt(0) will return the + * smallest key and keyAt(size()-1) will return the largest + * key.

+ */ + public int keyAt(int index) { + return mKeys[index]; + } + + /** + * Given an index in the range 0...size()-1, returns + * the value from the indexth key-value mapping that this + * SparseIntArray stores. + * + *

The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * valueAt(0) will return the value associated with the + * smallest key and valueAt(size()-1) will return the value + * associated with the largest key.

+ */ + public int valueAt(int index) { + return mValues[index]; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + return binarySearch(mKeys, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(int value) { + for (int i = 0; i < mSize; ++i) { + if (mValues[i] == value) { + return i; + } + } + return -1; + } + + /** + * Removes all key-value mappings from this SparseIntArray. + */ + public void clear() { + mSize = 0; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, int value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + mKeys = appendElementIntoIntArray(mKeys, mSize, key); + mValues = appendElementIntoIntArray(mValues, mSize, value); + mSize++; + } + + private int binarySearch(int[] array, int size, int value) { + int lo = 0; + int hi = size - 1; + + while (lo <= hi) { + int mid = (lo + hi) >>> 1; + int midVal = array[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + private int[] appendElementIntoIntArray(int[] array, int currentSize, int element) { + if (currentSize > array.length) { + throw new IllegalArgumentException("Bad currentSize, originalSize: " + array.length + " currentSize: " + currentSize); + } + if (currentSize + 1 > array.length) { + int[] newArray = new int[SparseIntArray.growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, currentSize); + array = newArray; + } + array[currentSize] = element; + return array; + } + + private int[] insertElementIntoIntArray(int[] array, int currentSize, int index, int element) { + if (currentSize > array.length) { + throw new IllegalArgumentException("Bad currentSize, originalSize: " + array.length + " currentSize: " + currentSize); + } + + if (currentSize + 1 <= array.length) { + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + int[] newArray = new int[SparseIntArray.growSize(currentSize)]; + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; + } + + /** + * {@inheritDoc} + * + *

This implementation composes a string by iterating over its mappings. + */ + @Override + public String toString() { + if (size() <= 0) { + return "{}"; + } + + StringBuilder buffer = new StringBuilder(mSize * 28); + buffer.append('{'); + for (int i = 0; i < mSize; i++) { + if (i > 0) { + buffer.append(", "); + } + int key = keyAt(i); + buffer.append(key); + buffer.append('='); + int value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/third-party/bsdiff-util/.gitignore b/third-party/bsdiff-util/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/third-party/bsdiff-util/.gitignore @@ -0,0 +1 @@ +/build diff --git a/third-party/bsdiff-util/LICENSE.txt b/third-party/bsdiff-util/LICENSE.txt new file mode 100644 index 00000000..650abd25 --- /dev/null +++ b/third-party/bsdiff-util/LICENSE.txt @@ -0,0 +1,27 @@ +BSD License + +Copyright (C) 2016 THL A29 Limited, a Tencent company. +Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) +Copyright 2003-2005 Colin Percival +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted providing that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/bsdiff-util/build.gradle b/third-party/bsdiff-util/build.gradle new file mode 100644 index 00000000..4153cea7 --- /dev/null +++ b/third-party/bsdiff-util/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'java' + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +task buildSdk(type: Copy, dependsOn: [build]) { + from('build/libs') { + include '*.jar' + exclude '*javadoc.jar' + exclude '*-sources.jar' + } + into(rootProject.file("buildSdk/android")) +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/third-party/bsdiff-util/gradle.properties b/third-party/bsdiff-util/gradle.properties new file mode 100644 index 00000000..7c4e5751 --- /dev/null +++ b/third-party/bsdiff-util/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=bsdiff-util +POM_NAME=BsDiff Util +POM_PACKAGING=jar \ No newline at end of file diff --git a/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSDiff.java b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSDiff.java new file mode 100644 index 00000000..545c7664 --- /dev/null +++ b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSDiff.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2016 THL A29 Limited, a Tencent company. + * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.tencent.tinker.bsdiff; + + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Java Binary Diff utility. Based on bsdiff (v4.2) by Colin Percival (see http://www.daemonology.net/bsdiff/ ) and distributed under BSD license. + * Running this on large files will probably require an increase of the default maximum heap size (use java -Xmx300m) + */ +public class BSDiff { + + //private static final String VERSION = "jbdiff-0.1.0.1"; + + // This is + private static final byte[] MAGIC_BYTES = new byte[]{0x4D, 0x69, 0x63, + 0x72, 0x6F, 0x4D, 0x73, 0x67}; + + private static void split(int[] arrayI, int[] arrayV, int start, int len, int h) { + + int i, j, k, x, tmp, jj, kk; + + if (len < 16) { + for (k = start; k < start + len; k += j) { + j = 1; + x = arrayV[arrayI[k] + h]; + for (i = 1; k + i < start + len; i++) { + if (arrayV[arrayI[k + i] + h] < x) { + x = arrayV[arrayI[k + i] + h]; + j = 0; + } + + if (arrayV[arrayI[k + i] + h] == x) { + tmp = arrayI[k + j]; + arrayI[k + j] = arrayI[k + i]; + arrayI[k + i] = tmp; + j++; + } + + } + + for (i = 0; i < j; i++) { + arrayV[arrayI[k + i]] = k + j - 1; + } + if (j == 1) { + arrayI[k] = -1; + } + } + + return; + } + + x = arrayV[arrayI[start + len / 2] + h]; + jj = 0; + kk = 0; + for (i = start; i < start + len; i++) { + if (arrayV[arrayI[i] + h] < x) { + jj++; + } + if (arrayV[arrayI[i] + h] == x) { + kk++; + } + } + + jj += start; + kk += jj; + + i = start; + j = 0; + k = 0; + while (i < jj) { + if (arrayV[arrayI[i] + h] < x) { + i++; + } else if (arrayV[arrayI[i] + h] == x) { + tmp = arrayI[i]; + arrayI[i] = arrayI[jj + j]; + arrayI[jj + j] = tmp; + j++; + } else { + tmp = arrayI[i]; + arrayI[i] = arrayI[kk + k]; + arrayI[kk + k] = tmp; + k++; + } + + } + + while (jj + j < kk) { + if (arrayV[arrayI[jj + j] + h] == x) { + j++; + } else { + tmp = arrayI[jj + j]; + arrayI[jj + j] = arrayI[kk + k]; + arrayI[kk + k] = tmp; + k++; + } + + } + + if (jj > start) { + split(arrayI, arrayV, start, jj - start, h); + } + + for (i = 0; i < kk - jj; i++) { + arrayV[arrayI[jj + i]] = kk - 1; + } + + if (jj == kk - 1) { + arrayI[jj] = -1; + } + + if (start + len > kk) { + split(arrayI, arrayV, kk, start + len - kk, h); + } + + } + + /** + * Fast suffix sporting. Larsson and Sadakane's qsufsort algorithm. See + * http://www.cs.lth.se/Research/Algorithms/Papers/jesper5.ps + */ + private static void qsufsort(int[] arrayI, int[] arrayV, byte[] oldBuf, int oldsize) { + + // int oldsize = oldBuf.length; + int[] buckets = new int[256]; + + // No need to do that in Java. + // for ( int i = 0; i < 256; i++ ) { + // buckets[i] = 0; + // } + + for (int i = 0; i < oldsize; i++) { + buckets[oldBuf[i] & 0xff]++; + } + + for (int i = 1; i < 256; i++) { + buckets[i] += buckets[i - 1]; + } + + for (int i = 255; i > 0; i--) { + buckets[i] = buckets[i - 1]; + } + + buckets[0] = 0; + + for (int i = 0; i < oldsize; i++) { + arrayI[++buckets[oldBuf[i] & 0xff]] = i; + } + + arrayI[0] = oldsize; + for (int i = 0; i < oldsize; i++) { + arrayV[i] = buckets[oldBuf[i] & 0xff]; + } + arrayV[oldsize] = 0; + + for (int i = 1; i < 256; i++) { + if (buckets[i] == buckets[i - 1] + 1) { + arrayI[buckets[i]] = -1; + } + } + + arrayI[0] = -1; + + for (int h = 1; arrayI[0] != -(oldsize + 1); h += h) { + int len = 0; + int i; + for (i = 0; i < oldsize + 1;) { + if (arrayI[i] < 0) { + len -= arrayI[i]; + i -= arrayI[i]; + } else { + // if(len) I[i-len]=-len; + if (len != 0) { + arrayI[i - len] = -len; + } + len = arrayV[arrayI[i]] + 1 - i; + split(arrayI, arrayV, i, len, h); + i += len; + len = 0; + } + + } + + if (len != 0) { + arrayI[i - len] = -len; + } + } + + for (int i = 0; i < oldsize + 1; i++) { + arrayI[arrayV[i]] = i; + } + } + + + /** + * 分别将 oldBufd[start..oldSize] 和 oldBufd[end..oldSize] 与 newBuf[newBufOffset...newSize] 进行匹配, + * 返回他们中的最长匹配长度,并且将最长匹配的开始位置记录到pos.value中。 + */ + private static int search(int[] arrayI, byte[] oldBuf, int oldSize, byte[] newBuf, int newSize, int newBufOffset, int start, int end, IntByRef pos) { + + if (end - start < 2) { + int x = matchlen(oldBuf, oldSize, arrayI[start], newBuf, newSize, newBufOffset); + int y = matchlen(oldBuf, oldSize, arrayI[end], newBuf, newSize, newBufOffset); + + if (x > y) { + pos.value = arrayI[start]; + return x; + } else { + pos.value = arrayI[end]; + return y; + } + } + + // binary search + int x = start + (end - start) / 2; + if (memcmp(oldBuf, oldSize, arrayI[x], newBuf, newSize, newBufOffset) < 0) { + return search(arrayI, oldBuf, oldSize, newBuf, newSize, newBufOffset, x, end, pos); // Calls itself recursively + } else { + return search(arrayI, oldBuf, oldSize, newBuf, newSize, newBufOffset, start, x, pos); + } + } + + + /** + * Count the number of bytes that match in oldBuf[oldOffset...oldSize] and newBuf[newOffset...newSize] + */ + private static int matchlen(byte[] oldBuf, int oldSize, int oldOffset, byte[] newBuf, int newSize, int newOffset) { + + int end = Math.min(oldSize - oldOffset, newSize - newOffset); + for (int i = 0; i < end; i++) { + if (oldBuf[oldOffset + i] != newBuf[newOffset + i]) { + return i; + } + } + return end; + } + + /** + * Compare two byte array segments to see if they are equal + * + * return 1 if s1[s1offset...s1Size] is bigger than s2[s2offset...s2Size] otherwise return -1 + */ + private static int memcmp(byte[] s1, int s1Size, int s1offset, byte[] s2, int s2Size, int s2offset) { + + int n = s1Size - s1offset; + + if (n > (s2Size - s2offset)) { + n = s2Size - s2offset; + } + + for (int i = 0; i < n; i++) { + + if (s1[i + s1offset] != s2[i + s2offset]) { + return s1[i + s1offset] < s2[i + s2offset] ? -1 : 1; + } + } + return 0; + } + + + public static void bsdiff(File oldFile, File newFile, File diffFile) throws IOException { + InputStream oldInputStream = new BufferedInputStream(new FileInputStream(oldFile)); + InputStream newInputStream = new BufferedInputStream(new FileInputStream(newFile)); + OutputStream diffOutputStream = new FileOutputStream(diffFile); + try { + byte[] diffBytes = bsdiff(oldInputStream, (int) oldFile.length(), newInputStream, (int) newFile.length()); + diffOutputStream.write(diffBytes); + } finally { + diffOutputStream.close(); + } + } + + + public static byte[] bsdiff(InputStream oldInputStream, int oldsize, InputStream newInputStream, int newsize) throws IOException { + + byte[] oldBuf = new byte[oldsize]; + + BSUtil.readFromStream(oldInputStream, oldBuf, 0, oldsize); + oldInputStream.close(); + + byte[] newBuf = new byte[newsize]; + BSUtil.readFromStream(newInputStream, newBuf, 0, newsize); + newInputStream.close(); + + return bsdiff(oldBuf, oldsize, newBuf, newsize); + } + + + public static byte[] bsdiff(byte[] oldBuf, int oldsize, byte[] newBuf, int newsize) throws IOException { + + int[] arrayI = new int[oldsize + 1]; + qsufsort(arrayI, new int[oldsize + 1], oldBuf, oldsize); + + // diff block + int diffBLockLen = 0; + byte[] diffBlock = new byte[newsize]; + + // extra block + int extraBlockLen = 0; + byte[] extraBlock = new byte[newsize]; + + /* + * Diff file is composed as follows: + * + * Header (32 bytes) Data (from offset 32 to end of file) + * + * Header: + * Offset 0, length 8 bytes: file magic "MicroMsg" + * Offset 8, length 8 bytes: length of compressed ctrl block + * Offset 16, length 8 bytes: length of compressed diff block + * Offset 24, length 8 bytes: length of new file + * + * Data: + * 32 (length ctrlBlockLen): ctrlBlock (bzip2) + * 32 + ctrlBlockLen (length diffBlockLen): diffBlock (bzip2) + * 32 + ctrlBlockLen + diffBlockLen (to end of file): extraBlock (bzip2) + * + * ctrlBlock comprises a set of records, each record 12 bytes. + * A record comprises 3 x 32 bit integers. The ctrlBlock is not compressed. + */ + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream diffOut = new DataOutputStream(byteOut); + + // Write as much of header as we have now. Size of ctrlBlock and diffBlock must be filled in later. + diffOut.write(MAGIC_BYTES); + diffOut.writeLong(-1); // place holder for ctrlBlockLen + diffOut.writeLong(-1); // place holder for diffBlockLen + diffOut.writeLong(newsize); + diffOut.flush(); + + GZIPOutputStream bzip2Out = new GZIPOutputStream(diffOut); + DataOutputStream dataOut = new DataOutputStream(bzip2Out); + + int oldscore, scsc; + + int overlap, ss, lens; + int i; + int scan = 0; + int matchLen = 0; + int lastscan = 0; + int lastpos = 0; + int lastoffset = 0; + + IntByRef pos = new IntByRef(); + // int ctrlBlockLen = 0; + + while (scan < newsize) { + oldscore = 0; + + for (scsc = scan += matchLen; scan < newsize; scan++) { + // oldBuf[0...oldsize] newBuf[scan...newSize]. pos.value,scan + matchLen = search(arrayI, oldBuf, oldsize, newBuf, newsize, scan, 0, oldsize, pos); + + for (; scsc < scan + matchLen; scsc++) { + if ((scsc + lastoffset < oldsize) && (oldBuf[scsc + lastoffset] == newBuf[scsc])) { + oldscore++; + } + } + + if (((matchLen == oldscore) && (matchLen != 0)) || (matchLen > oldscore + 8)) { + break; + } + + if ((scan + lastoffset < oldsize) && (oldBuf[scan + lastoffset] == newBuf[scan])) { + oldscore--; + } + } + + if ((matchLen != oldscore) || (scan == newsize)) { + + int equalNum = 0; + int sf = 0; + int lenFromOld = 0; + for (i = 0; (lastscan + i < scan) && (lastpos + i < oldsize);) { + if (oldBuf[lastpos + i] == newBuf[lastscan + i]) { + equalNum++; + } + i++; + if (equalNum * 2 - i > sf * 2 - lenFromOld) { + sf = equalNum; + lenFromOld = i; + } + } + + int lenb = 0; + if (scan < newsize) { + equalNum = 0; + int sb = 0; + for (i = 1; (scan >= lastscan + i) && (pos.value >= i); i++) { + if (oldBuf[pos.value - i] == newBuf[scan - i]) { + equalNum++; + } + if (equalNum * 2 - i > sb * 2 - lenb) { + sb = equalNum; + lenb = i; + } + } + } + + if (lastscan + lenFromOld > scan - lenb) { + overlap = (lastscan + lenFromOld) - (scan - lenb); + equalNum = 0; + ss = 0; + lens = 0; + for (i = 0; i < overlap; i++) { + if (newBuf[lastscan + lenFromOld - overlap + i] == oldBuf[lastpos + lenFromOld - overlap + i]) { + equalNum++; + } + if (newBuf[scan - lenb + i] == oldBuf[pos.value - lenb + i]) { + equalNum--; + } + if (equalNum > ss) { + ss = equalNum; + lens = i + 1; + } + } + + lenFromOld += lens - overlap; + lenb -= lens; + } + + // ? byte casting introduced here -- might affect things + for (i = 0; i < lenFromOld; i++) { + diffBlock[diffBLockLen + i] = (byte) (newBuf[lastscan + i] - oldBuf[lastpos + i]); + } + + for (i = 0; i < (scan - lenb) - (lastscan + lenFromOld); i++) { + extraBlock[extraBlockLen + i] = newBuf[lastscan + lenFromOld + i]; + } + + diffBLockLen += lenFromOld; + extraBlockLen += (scan - lenb) - (lastscan + lenFromOld); + + // Write control block entry (3 x int) + dataOut.writeInt(lenFromOld); // oldBuf + dataOut.writeInt((scan - lenb) - (lastscan + lenFromOld)); // diffBufextraBlock + dataOut.writeInt((pos.value - lenb) - (lastpos + lenFromOld)); // oldBuf + + lastscan = scan - lenb; + lastpos = pos.value - lenb; + lastoffset = pos.value - scan; + } // end if + } // end while loop + + dataOut.flush(); + bzip2Out.finish(); + + // now compressed ctrlBlockLen + int ctrlBlockLen = diffOut.size() - BSUtil.HEADER_SIZE; + + // GZIPOutputStream gzOut; + + /* + * Write diff block + */ + bzip2Out = new GZIPOutputStream(diffOut); + bzip2Out.write(diffBlock, 0, diffBLockLen); + bzip2Out.finish(); + bzip2Out.flush(); + int diffBlockLen = diffOut.size() - ctrlBlockLen - BSUtil.HEADER_SIZE; + // System.err.println( "Diff: diffBlockLen=" + diffBlockLen ); + + /* + * Write extra block + */ + bzip2Out = new GZIPOutputStream(diffOut); + bzip2Out.write(extraBlock, 0, extraBlockLen); + bzip2Out.finish(); + bzip2Out.flush(); + + diffOut.close(); + + /* + * Write missing header info. + */ + ByteArrayOutputStream byteHeaderOut = new ByteArrayOutputStream(BSUtil.HEADER_SIZE); + DataOutputStream headerOut = new DataOutputStream(byteHeaderOut); + headerOut.write(MAGIC_BYTES); + headerOut.writeLong(ctrlBlockLen); // place holder for ctrlBlockLen + headerOut.writeLong(diffBlockLen); // place holder for diffBlockLen + headerOut.writeLong(newsize); + headerOut.close(); + + // Copy header information into the diff + byte[] diffBytes = byteOut.toByteArray(); + byte[] headerBytes = byteHeaderOut.toByteArray(); + + System.arraycopy(headerBytes, 0, diffBytes, 0, headerBytes.length); + + return diffBytes; + } + +// /** +// * Run JBDiff from the command line. Params: oldfile newfile difffile. diff +// * file will be created. +// */ +// public static void main(String[] arg) throws IOException { +// +// if (arg.length != 3) { +// System.err.println("usage example: java -Xmx250m JBDiff oldfile newfile patchfile\n"); +// return; +// } +// File oldFile = new File(arg[0]); +// File newFile = new File(arg[1]); +// File diffFile = new File(arg[2]); +// +// bsdiff(oldFile, newFile, diffFile); +// +// } + + private static class IntByRef { + private int value; + } +} diff --git a/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java new file mode 100644 index 00000000..2e8d7269 --- /dev/null +++ b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2016 THL A29 Limited, a Tencent company. + * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.tencent.tinker.bsdiff; + + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.zip.GZIPInputStream; + + +/** + * Java Binary patcher (based on bspatch by Joe Desbonnet, joe@galway.net (JBPatch)) + */ +public class BSPatch { + + /** + * the patch process is end up successfully + */ + public static final int RETURN_SUCCESS = 1; + + /** + * diffFile is null, or the diffFile does not exist + */ + public static final int RETURN_DIFF_FILE_ERR = 2; + + /** + * oldFile is null, or the oldFile does not exist + */ + public static final int RETURN_OLD_FILE_ERR = 3; + + /** + * newFile is null, or can not create the newFile + */ + public static final int RETURN_NEW_FILE_ERR = 4; + + /** + * BSPatch using less memory size. + * Memory size = diffFile size + max block size + * + */ + public static int patchLessMemory(RandomAccessFile oldFile, File newFile, File diffFile, int extLen) throws IOException { + if (oldFile == null || oldFile.length() <= 0) { + return RETURN_OLD_FILE_ERR; + } + if (newFile == null) { + return RETURN_NEW_FILE_ERR; + } + if (diffFile == null || diffFile.length() <= 0) { + return RETURN_DIFF_FILE_ERR; + } + + byte[] diffBytes = new byte[(int) diffFile.length()]; + InputStream diffInputStream = new FileInputStream(diffFile); + try { + BSUtil.readFromStream(diffInputStream, diffBytes, 0, diffBytes.length); + } finally { + diffInputStream.close(); + } + return patchLessMemory(oldFile, (int) oldFile.length(), diffBytes, diffBytes.length, newFile, extLen); + } + + /** + * BSPatch using less memory size. + * Memory size = diffFile size + max block size + * extLen the length of the apk external info. set 0 if has no external info. + */ + public static int patchLessMemory(RandomAccessFile oldFile, int oldsize, byte[] diffBuf, int diffSize, File newFile, int extLen) throws IOException { + + if (oldFile == null || oldsize <= 0) { + return RETURN_OLD_FILE_ERR; + } + if (newFile == null) { + return RETURN_NEW_FILE_ERR; + } + if (diffBuf == null || diffSize <= 0) { + return RETURN_DIFF_FILE_ERR; + } + + int commentLenPos = oldsize - extLen - 2; + if (commentLenPos <= 2) { + return RETURN_OLD_FILE_ERR; + } + + DataInputStream diffIn = new DataInputStream(new ByteArrayInputStream(diffBuf, 0, diffSize)); + + diffIn.skip(8); // skip headerMagic at header offset 0 (length 8 bytes) + long ctrlBlockLen = diffIn.readLong(); // ctrlBlockLen after bzip2 compression at heater offset 8 (length 8 bytes) + long diffBlockLen = diffIn.readLong(); // diffBlockLen after bzip2 compression at header offset 16 (length 8 bytes) + int newsize = (int) diffIn.readLong(); // size of new file at header offset 24 (length 8 bytes) + + diffIn.close(); + + InputStream in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(BSUtil.HEADER_SIZE); + DataInputStream ctrlBlockIn = new DataInputStream(new GZIPInputStream(in)); + + in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(ctrlBlockLen + BSUtil.HEADER_SIZE); + InputStream diffBlockIn = new GZIPInputStream(in); + + in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE); + InputStream extraBlockIn = new GZIPInputStream(in); + + OutputStream outStream = new FileOutputStream(newFile); + try { + int oldpos = 0; + int newpos = 0; + int[] ctrl = new int[3]; + + // int nbytes; + while (newpos < newsize) { + + for (int i = 0; i <= 2; i++) { + ctrl[i] = ctrlBlockIn.readInt(); + } + + if (newpos + ctrl[0] > newsize) { + outStream.close(); + return RETURN_DIFF_FILE_ERR; + } + + // Read ctrl[0] bytes from diffBlock stream + byte[] buffer = new byte[ctrl[0]]; + if (!BSUtil.readFromStream(diffBlockIn, buffer, 0, ctrl[0])) { + outStream.close(); + return RETURN_DIFF_FILE_ERR; + } + + byte[] oldBuffer = new byte[ctrl[0]]; + if (oldFile.read(oldBuffer, 0, ctrl[0]) < ctrl[0]) { + outStream.close(); + return RETURN_DIFF_FILE_ERR; + } + for (int i = 0; i < ctrl[0]; i++) { + if (oldpos + i == commentLenPos) { + oldBuffer[i] = 0; + oldBuffer[i + 1] = 0; + } + + if ((oldpos + i >= 0) && (oldpos + i < oldsize)) { + buffer[i] += oldBuffer[i]; + } + } + outStream.write(buffer); + +// System.out.println(""+ctrl[0]+ ", " + ctrl[1]+ ", " + ctrl[2]); + + newpos += ctrl[0]; + oldpos += ctrl[0]; + + if (newpos + ctrl[1] > newsize) { + outStream.close(); + return RETURN_DIFF_FILE_ERR; + } + + buffer = new byte[ctrl[1]]; + if (!BSUtil.readFromStream(extraBlockIn, buffer, 0, ctrl[1])) { + outStream.close(); + return RETURN_DIFF_FILE_ERR; + } + outStream.write(buffer); + outStream.flush(); + + newpos += ctrl[1]; + oldpos += ctrl[2]; + oldFile.seek(oldpos); + } + ctrlBlockIn.close(); + diffBlockIn.close(); + extraBlockIn.close(); + } finally { + oldFile.close(); + outStream.close(); + } + return RETURN_SUCCESS; + } + + /** + * This patch method is fast ,but using more memory. + * Memory size = oldBuf + diffBuf + newBuf + * + */ + public static int patchFast(File oldFile, File newFile, File diffFile, int extLen) throws IOException { + if (oldFile == null || oldFile.length() <= 0) { + return RETURN_OLD_FILE_ERR; + } + if (newFile == null) { + return RETURN_NEW_FILE_ERR; + } + if (diffFile == null || diffFile.length() <= 0) { + return RETURN_DIFF_FILE_ERR; + } + + InputStream oldInputStream = new BufferedInputStream(new FileInputStream(oldFile)); + byte[] diffBytes = new byte[(int) diffFile.length()]; + InputStream diffInputStream = new FileInputStream(diffFile); + try { + BSUtil.readFromStream(diffInputStream, diffBytes, 0, diffBytes.length); + } finally { + diffInputStream.close(); + } + + byte[] newBytes = patchFast(oldInputStream, (int) oldFile.length(), diffBytes, extLen); + + OutputStream newOutputStream = new FileOutputStream(newFile); + try { + newOutputStream.write(newBytes); + } finally { + newOutputStream.close(); + } + return RETURN_SUCCESS; + } + + /** + * This patch method is fast ,but using more memory. + * Memory size = oldBuf + diffBuf + newBuf + * + */ + public static int patchFast(InputStream oldInputStream, InputStream diffInputStream, File newFile) throws IOException { + if (oldInputStream == null) { + return RETURN_OLD_FILE_ERR; + } + if (newFile == null) { + return RETURN_NEW_FILE_ERR; + } + if (diffInputStream == null) { + return RETURN_DIFF_FILE_ERR; + } + + byte[] oldBytes = BSUtil.inputStreamToByte(oldInputStream); + byte[] diffBytes = BSUtil.inputStreamToByte(diffInputStream); + + byte[] newBytes = patchFast(oldBytes, oldBytes.length, diffBytes, diffBytes.length, 0); + + OutputStream newOutputStream = new FileOutputStream(newFile); + try { + newOutputStream.write(newBytes); + } finally { + newOutputStream.close(); + } + return RETURN_SUCCESS; + } + + public static byte[] patchFast(InputStream oldInputStream, InputStream diffInputStream) throws IOException { + if (oldInputStream == null) { + return null; + } + + if (diffInputStream == null) { + return null; + } + + byte[] oldBytes = BSUtil.inputStreamToByte(oldInputStream); + byte[] diffBytes = BSUtil.inputStreamToByte(diffInputStream); + + byte[] newBytes = patchFast(oldBytes, oldBytes.length, diffBytes, diffBytes.length, 0); + return newBytes; + } + + /** + * This patch method is fast ,but using more memory. + * Memory size = oldBuf + diffBuf + newBuf + */ + public static byte[] patchFast(InputStream oldInputStream, int oldsize, byte[] diffBytes, int extLen) throws IOException { + // Read in old file (file to be patched) to oldBuf + byte[] oldBuf = new byte[oldsize]; + BSUtil.readFromStream(oldInputStream, oldBuf, 0, oldsize); + oldInputStream.close(); + + return BSPatch.patchFast(oldBuf, oldsize, diffBytes, diffBytes.length, extLen); + } + + /** + * This patch method is fast ,but using more memory. + * Memory size = oldBuf + diffBuf + newBuf + */ + public static byte[] patchFast(byte[] oldBuf, int oldsize, byte[] diffBuf, int diffSize, int extLen) throws IOException { + DataInputStream diffIn = new DataInputStream(new ByteArrayInputStream(diffBuf, 0, diffSize)); + + diffIn.skip(8); // skip headerMagic at header offset 0 (length 8 bytes) + long ctrlBlockLen = diffIn.readLong(); // ctrlBlockLen after bzip2 compression at heater offset 8 (length 8 bytes) + long diffBlockLen = diffIn.readLong(); // diffBlockLen after bzip2 compression at header offset 16 (length 8 bytes) + int newsize = (int) diffIn.readLong(); // size of new file at header offset 24 (length 8 bytes) + + diffIn.close(); + + InputStream in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(BSUtil.HEADER_SIZE); + DataInputStream ctrlBlockIn = new DataInputStream(new GZIPInputStream(in)); + + in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(ctrlBlockLen + BSUtil.HEADER_SIZE); + InputStream diffBlockIn = new GZIPInputStream(in); + + in = new ByteArrayInputStream(diffBuf, 0, diffSize); + in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE); + InputStream extraBlockIn = new GZIPInputStream(in); + + // byte[] newBuf = new byte[newsize + 1]; + byte[] newBuf = new byte[newsize]; + + int oldpos = 0; + int newpos = 0; + int[] ctrl = new int[3]; + + // int nbytes; + while (newpos < newsize) { + + for (int i = 0; i <= 2; i++) { + ctrl[i] = ctrlBlockIn.readInt(); + } + + if (newpos + ctrl[0] > newsize) { + throw new IOException("Corrupt by wrong patch file."); + } + + // Read ctrl[0] bytes from diffBlock stream + if (!BSUtil.readFromStream(diffBlockIn, newBuf, newpos, ctrl[0])) { + throw new IOException("Corrupt by wrong patch file."); + } + + for (int i = 0; i < ctrl[0]; i++) { + if ((oldpos + i >= 0) && (oldpos + i < oldsize)) { + newBuf[newpos + i] += oldBuf[oldpos + i]; + } + } + + newpos += ctrl[0]; + oldpos += ctrl[0]; + + if (newpos + ctrl[1] > newsize) { + throw new IOException("Corrupt by wrong patch file."); + } + + if (!BSUtil.readFromStream(extraBlockIn, newBuf, newpos, ctrl[1])) { + throw new IOException("Corrupt by wrong patch file."); + } + + newpos += ctrl[1]; + oldpos += ctrl[2]; + } + ctrlBlockIn.close(); + diffBlockIn.close(); + extraBlockIn.close(); + + return newBuf; + } + +} \ No newline at end of file diff --git a/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSUtil.java b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSUtil.java new file mode 100644 index 00000000..eec30304 --- /dev/null +++ b/third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSUtil.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 THL A29 Limited, a Tencent company. + * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.tencent.tinker.bsdiff; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class BSUtil { + + // JBDiff extensions by Stefan.Liebig@compeople.de: + // + // - introduced a HEADER_SIZE constant here + + /** + * Length of the diff file header. + */ + public static final int HEADER_SIZE = 32; + public static final int BUFFER_SIZE = 8192; + + + /** + * Read from input stream and fill the given buffer from the given offset up + * to length len. + */ + public static final boolean readFromStream(InputStream in, byte[] buf, int offset, int len) throws IOException { + + int totalBytesRead = 0; + while (totalBytesRead < len) { + int bytesRead = in.read(buf, offset + totalBytesRead, len - totalBytesRead); + if (bytesRead < 0) { + return false; + } + totalBytesRead += bytesRead; + } + return true; + } + + /** + * input stream to byte + * @param in InputStream + * @return byte[] + * @throws IOException + */ + public static byte[] inputStreamToByte(InputStream in) throws IOException { + + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] data = new byte[BUFFER_SIZE]; + int count = -1; + while ((count = in.read(data, 0, BUFFER_SIZE)) != -1) { + outStream.write(data, 0, count); + } + + data = null; + return outStream.toByteArray(); + } +} \ No newline at end of file diff --git a/third-party/seven-zip/.gitignore b/third-party/seven-zip/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/third-party/seven-zip/.gitignore @@ -0,0 +1 @@ +/build diff --git a/third-party/seven-zip/License.txt b/third-party/seven-zip/License.txt new file mode 100644 index 00000000..2c6b3074 --- /dev/null +++ b/third-party/seven-zip/License.txt @@ -0,0 +1,52 @@ + 7-Zip source code + ~~~~~~~~~~~~~~~~~ + License for use and distribution + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 7-Zip Copyright (C) 1999-2016 Igor Pavlov. + + Licenses for files are: + + 1) CPP/7zip/Compress/Rar* files: GNU LGPL + unRAR restriction + 2) All other files: GNU LGPL + + The GNU LGPL + unRAR restriction means that you must follow both + GNU LGPL rules and unRAR restriction rules. + + + GNU LGPL information + -------------------- + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + unRAR restriction + ----------------- + + The decompression engine for RAR archives was developed using source + code of unRAR program. + All copyrights to original unRAR code are owned by Alexander Roshal. + + The license for original unRAR code has the following restriction: + + The unRAR sources cannot be used to re-create the RAR compression algorithm, + which is proprietary. Distribution of modified unRAR sources in separate form + or as a part of other software is permitted, provided that it is clearly + stated in the documentation and source comments that the code may + not be used to develop a RAR (WinRAR) compatible archiver. + + + -- + Igor Pavlov diff --git a/third-party/seven-zip/build.gradle b/third-party/seven-zip/build.gradle new file mode 100644 index 00000000..94e1be6e --- /dev/null +++ b/third-party/seven-zip/build.gradle @@ -0,0 +1,68 @@ +apply plugin: 'maven' +apply plugin: 'maven-publish' +apply plugin: 'com.jfrog.bintray' + +//hardcode 1.0.0 +version "1.0.0" +group rootProject.ext.GROUP + + +publishing { + publications { + SevenZipPub(MavenPublication) { + artifactId project.getName() + groupId group + artifact("executable/SevenZip-linux-x86_32.exe") { + classifier "linux-x86_32" + extension "exe" + } + artifact("executable/SevenZip-linux-x86_64.exe") { + classifier "linux-x86_64" + extension "exe" + } + artifact("executable/SevenZip-windows-x86_32.exe") { + classifier "windows-x86_32" + extension "exe" + } + artifact("executable/SevenZip-windows-x86_64.exe") { + classifier "windows-x86_64" + extension "exe" + } + artifact("executable/SevenZip-osx-x86_64.exe") { + classifier "osx-x86_64" + extension "exe" + } + } + } +} + +task buildAndPublishLocalMaven(dependsOn: 'publishSevenZipPubPublicationToMavenLocal') { + +} + +artifacts { + archives(file("executable/SevenZip-linux-x86_32.exe")) { + classifier "linux-x86_32" + extension "exe" + } + archives(file("executable/SevenZip-linux-x86_64.exe")) { + classifier "linux-x86_64" + extension "exe" + } + archives(file("executable/SevenZip-windows-x86_32.exe")) { + classifier "windows-x86_32" + extension "exe" + } + archives(file("executable/SevenZip-windows-x86_64.exe")) { + classifier "windows-x86_64" + extension "exe" + } + archives(file("executable/SevenZip-osx-x86_64.exe")) { + classifier "osx-x86_64" + extension "exe" + } +} +//don't upload sevenZip default +if (hasProperty('UPLOAD_SEVEN_ZIP')) { + apply from: rootProject.file('gradle/gradle-mvn-push.gradle') +} diff --git a/third-party/seven-zip/executable/SevenZip-linux-x86_32.exe b/third-party/seven-zip/executable/SevenZip-linux-x86_32.exe new file mode 100755 index 00000000..f1c18e4b Binary files /dev/null and b/third-party/seven-zip/executable/SevenZip-linux-x86_32.exe differ diff --git a/third-party/seven-zip/executable/SevenZip-linux-x86_64.exe b/third-party/seven-zip/executable/SevenZip-linux-x86_64.exe new file mode 100644 index 00000000..927cc1ad Binary files /dev/null and b/third-party/seven-zip/executable/SevenZip-linux-x86_64.exe differ diff --git a/third-party/seven-zip/executable/SevenZip-osx-x86_64.exe b/third-party/seven-zip/executable/SevenZip-osx-x86_64.exe new file mode 100644 index 00000000..c40e062b Binary files /dev/null and b/third-party/seven-zip/executable/SevenZip-osx-x86_64.exe differ diff --git a/third-party/seven-zip/executable/SevenZip-windows-x86_32.exe b/third-party/seven-zip/executable/SevenZip-windows-x86_32.exe new file mode 100644 index 00000000..710bda2f Binary files /dev/null and b/third-party/seven-zip/executable/SevenZip-windows-x86_32.exe differ diff --git a/third-party/seven-zip/executable/SevenZip-windows-x86_64.exe b/third-party/seven-zip/executable/SevenZip-windows-x86_64.exe new file mode 100644 index 00000000..881b7dd9 Binary files /dev/null and b/third-party/seven-zip/executable/SevenZip-windows-x86_64.exe differ diff --git a/third-party/seven-zip/gradle.properties b/third-party/seven-zip/gradle.properties new file mode 100644 index 00000000..6c1444d4 --- /dev/null +++ b/third-party/seven-zip/gradle.properties @@ -0,0 +1,2 @@ +POM_ARTIFACT_ID=seven-zip +POM_NAME=seven-zip diff --git a/tinker-android/tinker-android-anno/.gitignore b/tinker-android/tinker-android-anno/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-android/tinker-android-anno/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-android/tinker-android-anno/build.gradle b/tinker-android/tinker-android-anno/build.gradle new file mode 100644 index 00000000..d74f33c2 --- /dev/null +++ b/tinker-android/tinker-android-anno/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'java' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + + resources { + srcDir 'src/main/resources' + } + } +} + +task buildSdk(type: Copy, dependsOn: [build]) { + from('build/libs') { + include '*.jar' + exclude '*javadoc.jar' + exclude '*-sources.jar' + } + into(rootProject.file("buildSdk/android")) +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/tinker-android/tinker-android-anno/gradle.properties b/tinker-android/tinker-android-anno/gradle.properties new file mode 100644 index 00000000..cd11cbce --- /dev/null +++ b/tinker-android/tinker-android-anno/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=tinker-android-anno +POM_NAME=Tinker Android Anno +POM_PACKAGING=jar \ No newline at end of file diff --git a/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/AnnotationProcessor.java b/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/AnnotationProcessor.java new file mode 100644 index 00000000..533a1c3c --- /dev/null +++ b/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/AnnotationProcessor.java @@ -0,0 +1,115 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.anno; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.LinkedHashSet; +import java.util.Scanner; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; + + +/** + * Tinker Annotations Processor + * + * Created by zhaoyuan on 16/3/31. + */ +@SupportedSourceVersion(SourceVersion.RELEASE_7) +public class AnnotationProcessor extends AbstractProcessor { + + private static final String SUFFIX = "$$DefaultLifeCycle"; + private static final String APPLICATION_TEMPLATE_PATH = "/TinkerAnnoApplication.tmpl"; + + @Override + public Set getSupportedAnnotationTypes() { + final Set supportedAnnotationTypes = new LinkedHashSet<>(); + + supportedAnnotationTypes.add(DefaultLifeCycle.class.getName()); + + return supportedAnnotationTypes; + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + processDefaultLifeCycle(roundEnv.getElementsAnnotatedWith(DefaultLifeCycle.class)); + return true; + } + + + private void processDefaultLifeCycle(Set elements) { + // DefaultLifeCycle + for (Element e : elements) { + DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class); + + String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString(); + String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.')); + lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1); + + String applicationClassName = ca.application(); + if (applicationClassName.startsWith(".")) { + applicationClassName = lifeCyclePackageName + applicationClassName; + } + String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.')); + applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1); + + String loaderClassName = ca.loaderClass(); + if (loaderClassName.startsWith(".")) { + loaderClassName = lifeCyclePackageName + loaderClassName; + } + + System.out.println("*"); + + final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH); + final Scanner scanner = new Scanner(is); + final String template = scanner.useDelimiter("\\A").next(); + final String fileContent = template + .replaceAll("%PACKAGE%", applicationPackageName) + .replaceAll("%APPLICATION%", applicationClassName) + .replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName) + .replaceAll("%TINKER_FLAGS%", "" + ca.flags()) + .replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName) + .replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag()); + + try { + JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri()); + Writer writer = fileObject.openWriter(); + try { + PrintWriter pw = new PrintWriter(writer); + pw.print(fileContent); + pw.flush(); + + } finally { + writer.close(); + } + } catch (IOException x) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString()); + } + } + } +} diff --git a/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/DefaultLifeCycle.java b/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/DefaultLifeCycle.java new file mode 100644 index 00000000..ddd5db1b --- /dev/null +++ b/tinker-android/tinker-android-anno/src/main/java/com/tencent/tinker/anno/DefaultLifeCycle.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotations + * + * Created by zhaoyuan on 16/3/31. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +@Inherited +public @interface DefaultLifeCycle { + + String application(); + + String loaderClass() default "com.tencent.tinker.loader.TinkerLoader"; + + int flags(); + + boolean loadVerifyFlag() default false; + + +} diff --git a/tinker-android/tinker-android-anno/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/tinker-android/tinker-android-anno/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000..5f7162da --- /dev/null +++ b/tinker-android/tinker-android-anno/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.tencent.tinker.anno.AnnotationProcessor diff --git a/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl b/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl new file mode 100644 index 00000000..a856f512 --- /dev/null +++ b/tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl @@ -0,0 +1,16 @@ +package %PACKAGE%; + +import com.tencent.tinker.loader.app.TinkerApplication; + +/** + * + * Generated application for tinker life cycle + * + */ +public class %APPLICATION% extends TinkerApplication { + + public %APPLICATION%() { + super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%); + } + +} \ No newline at end of file diff --git a/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/TestLifeCycle.java b/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/TestLifeCycle.java new file mode 100644 index 00000000..0cea5f12 --- /dev/null +++ b/tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/TestLifeCycle.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.anno.test; + +//import com.tencent.tinker.anno.DefaultLifeCycle; +// +///** +// * Test LifeCycle +// * +// * Created by zhaoyuan on 16/4/1. +// */ +//@DefaultLifeCycle(application = ".TestApplication", flags = 0x10, loaderClass="com.tencent.tinker.loader.TinkerLoader") +//public class TestLifeCycle { +// +//} diff --git a/tinker-android/tinker-android-lib/.gitignore b/tinker-android/tinker-android-lib/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-android/tinker-android-lib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-android/tinker-android-lib/build.gradle b/tinker-android/tinker-android-lib/build.gradle new file mode 100644 index 00000000..7f9c60b2 --- /dev/null +++ b/tinker-android/tinker-android-lib/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.library' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile project(':tinker-commons') + compile project(':tinker-android:tinker-android-loader') +} + +task buildSdk(type: Copy, dependsOn: [build]) { + from("$buildDir/outputs/aar/") { + include "${project.getName()}-release.aar" + } + + into(rootProject.file("buildSdk/android/")) + rename { String fileName -> + fileName.replace("release", "${version}") + } +} + +apply from: rootProject.file('gradle/android-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') + + + + diff --git a/tinker-android/tinker-android-lib/gradle.properties b/tinker-android/tinker-android-lib/gradle.properties new file mode 100644 index 00000000..a66d58cb --- /dev/null +++ b/tinker-android/tinker-android-lib/gradle.properties @@ -0,0 +1,2 @@ +POM_ARTIFACT_ID=tinker-android-lib +POM_NAME=Tinker Android Lib diff --git a/tinker-android/tinker-android-lib/proguard-rules.pro b/tinker-android/tinker-android-lib/proguard-rules.pro new file mode 100644 index 00000000..ca7ea794 --- /dev/null +++ b/tinker-android/tinker-android-lib/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/tinker-android/tinker-android-lib/src/androidTest/java/com/tencent/tinker/lib/patch/ApplicationTest.java b/tinker-android/tinker-android-lib/src/androidTest/java/com/tencent/tinker/lib/patch/ApplicationTest.java new file mode 100644 index 00000000..58b06d7d --- /dev/null +++ b/tinker-android/tinker-android-lib/src/androidTest/java/com/tencent/tinker/lib/patch/ApplicationTest.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/tinker-android/tinker-android-lib/src/main/AndroidManifest.xml b/tinker-android/tinker-android-lib/src/main/AndroidManifest.xml new file mode 100644 index 00000000..da7ffb48 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java new file mode 100644 index 00000000..f0f3055f --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java @@ -0,0 +1,85 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.listener; + +import android.content.Context; + +import com.tencent.tinker.lib.service.TinkerPatchService; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerServiceInternals; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/14. + */ +public class DefaultPatchListener implements PatchListener { + protected final Context context; + + public DefaultPatchListener(Context context) { + this.context = context; + } + + /** + * when we receive a patch, what would we do? + * you can overwrite it + * + * @param path + * @param isUpgrade + * @return + */ + @Override + public int onPatchReceived(String path, boolean isUpgrade) { + + int returnCode = patchCheck(path, isUpgrade); + + if (returnCode == ShareConstants.ERROR_PATCH_OK) { + TinkerPatchService.runPatchService(context, path, isUpgrade); + } else { + Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode, isUpgrade); + } + return returnCode; + + } + + protected int patchCheck(String path, boolean isUpgrade) { + Tinker manager = Tinker.with(context); + //check SharePrefenences also + if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) { + return ShareConstants.ERROR_PATCH_DISABLE; + } + File file = new File(path); + + if (!file.isFile() || !file.exists() || file.length() == 0) { + return ShareConstants.ERROR_PATCH_NOTEXIST; + } + + //patch service can not send request + if (manager.isPatchProcess()) { + return ShareConstants.ERROR_PATCH_INSERVICE; + } + + //if the patch service is running, pending + if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) { + return ShareConstants.ERROR_PATCH_RUNNING; + } + return ShareConstants.ERROR_PATCH_OK; + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/PatchListener.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/PatchListener.java new file mode 100644 index 00000000..7d65c555 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/PatchListener.java @@ -0,0 +1,24 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.listener; + +/** + * Created by zhangshaowen on 16/3/14. + */ +public interface PatchListener { + int onPatchReceived(String path, boolean isUpgrade); +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java new file mode 100644 index 00000000..1cbe4025 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; + +import com.tencent.tinker.lib.service.PatchResult; + +/** + * Created by zhangshaowen on 16/3/15. + */ +public abstract class AbstractPatch { + + public abstract boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult); +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BasePatchInternal.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BasePatchInternal.java new file mode 100644 index 00000000..5ef3d88d --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BasePatchInternal.java @@ -0,0 +1,101 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Created by zhangshaowen on 16/4/12. + */ +public class BasePatchInternal { + protected static final String TAG = "Tinker.BasePatchInternal"; + + protected static final String DEX_PATH = ShareConstants.DEX_PATH; + protected static final String SO_PATH = ShareConstants.SO_PATH; + protected static final String DEX_OPTIMIZE_PATH = ShareConstants.DEX_OPTIMIZE_PATH; + protected static final int MAX_EXTRACT_ATTEMPTS = ShareConstants.MAX_EXTRACT_ATTEMPTS; + protected static final String DEX_META_FILE = ShareConstants.DEX_META_FILE; + protected static final String SO_META_FILE = ShareConstants.SO_META_FILE; + protected static final String RES_META_FILE = ShareConstants.RES_META_FILE; + + protected static final int TYPE_DEX = ShareConstants.TYPE_DEX; + protected static final int TYPE_DEX_FOR_ART = ShareConstants.TYPE_DEX_FOR_ART; + protected static final int TYPE_Library = ShareConstants.TYPE_LIBRARY; + protected static final int TYPE_RESOURCE = ShareConstants.TYPE_RESOURCE; + + public static boolean extract(ZipFile zipFile, ZipEntry entryFile, File extractTo, String targetMd5, boolean isDex) throws IOException { + int numAttempts = 0; + boolean isExtractionSuccessful = false; + while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { + numAttempts++; + BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entryFile)); + FileOutputStream fos = new FileOutputStream(extractTo); + BufferedOutputStream out = new BufferedOutputStream(fos); + + TinkerLog.i(TAG, "try Extracting " + extractTo.getPath()); + + try { + byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; + int length = bis.read(buffer); + while (length != -1) { + out.write(buffer, 0, length); + length = bis.read(buffer); + } + } finally { + SharePatchFileUtil.closeQuietly(out); + SharePatchFileUtil.closeQuietly(bis); + } + + if (isDex) { + isExtractionSuccessful = SharePatchFileUtil.verifyDexFileMd5(extractTo, targetMd5); + } else { + isExtractionSuccessful = SharePatchFileUtil.verifyFileMd5(extractTo, targetMd5); + } + TinkerLog.i(TAG, "isExtractionSuccessful: %b", isExtractionSuccessful); + + if (!isExtractionSuccessful) { + extractTo.delete(); + if (extractTo.exists()) { + TinkerLog.e(TAG, "Failed to delete corrupted dex " + extractTo.getPath()); + } + } + } + + return isExtractionSuccessful; + } + + public static int getMetaCorruptedCode(int type) { + if (type == TYPE_DEX || type == TYPE_DEX_FOR_ART) { + return ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED; + } else if (type == TYPE_Library) { + return ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED; + } else if (type == TYPE_RESOURCE) { + return ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED; + } + return ShareConstants.ERROR_PACKAGE_CHECK_OK; + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BsDiffPatchInternal.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BsDiffPatchInternal.java new file mode 100644 index 00000000..45e241b2 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BsDiffPatchInternal.java @@ -0,0 +1,211 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.SystemClock; + +import com.tencent.tinker.bsdiff.BSPatch; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareBsDiffPatchInfo; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Created by zhangshaowen on 16/3/21. + */ +public class BsDiffPatchInternal extends BasePatchInternal { + private static final String TAG = "Tinker.BsDiffPatchInternal"; + + protected static boolean tryRecoverLibraryFiles(Tinker manager, ShareSecurityCheck checker, Context context, + String patchVersionDirectory, File patchFile, boolean isUpgradePatch) { + + if (!manager.isEnabledForNativeLib()) { + TinkerLog.w(TAG, "patch recover, library is not enabled"); + return true; + } + String libMeta = checker.getMetaContentMap().get(SO_META_FILE); + + if (libMeta == null) { + TinkerLog.w(TAG, "patch recover, library is not contained"); + return true; + } + long begin = SystemClock.elapsedRealtime(); + boolean result = patchLibraryExtractViaBsDiff(context, patchVersionDirectory, libMeta, patchFile, isUpgradePatch); + long cost = SystemClock.elapsedRealtime() - begin; + TinkerLog.i(TAG, "recover lib result:%b, cost:%d, isUpgradePatch:%b", result, cost, isUpgradePatch); + return result; + } + + + private static boolean patchLibraryExtractViaBsDiff(Context context, String patchVersionDirectory, String meta, File patchFile, boolean isUpgradePatch) { + String dir = patchVersionDirectory + "/" + SO_PATH + "/"; + return extractBsDiffInternals(context, dir, meta, patchFile, TYPE_Library, isUpgradePatch); + } + + private static boolean extractBsDiffInternals(Context context, String dir, String meta, File patchFile, int type, boolean isUpgradePatch) { + //parse + ArrayList patchList = new ArrayList<>(); + + ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, patchList); + + if (patchList.isEmpty()) { + TinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type)); + return true; + } + + File directory = new File(dir); + if (!directory.exists()) { + directory.mkdirs(); + } + //I think it is better to extract the raw files from apk + Tinker manager = Tinker.with(context); + ApplicationInfo applicationInfo = context.getApplicationInfo(); + if (applicationInfo == null) { + // Looks like running on a test Context, so just return without patching. + TinkerLog.w(TAG, "applicationInfo == null!!!!"); + return false; + } + ZipFile apk = null; + ZipFile patch = null; + try { + String apkPath = applicationInfo.sourceDir; + apk = new ZipFile(apkPath); + patch = new ZipFile(patchFile); + + for (ShareBsDiffPatchInfo info : patchList) { + long start = System.currentTimeMillis(); + + final String infoPath = info.path; + String patchRealPath; + if (infoPath.equals("")) { + patchRealPath = info.name; + } else { + patchRealPath = info.path + "/" + info.name; + } + final String fileMd5 = info.md5; + if (!SharePatchFileUtil.checkIfMd5Valid(fileMd5)) { + TinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, info.md5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + String middle; + + middle = info.path + "/" + info.name; + + File extractedFile = new File(dir + middle); + + //check file whether already exist + if (extractedFile.exists()) { + if (fileMd5.equals(SharePatchFileUtil.getMD5(extractedFile))) { + //it is ok, just continue + TinkerLog.w(TAG, "bsdiff file %s is already exist, and md5 match, just continue", extractedFile.getPath()); + continue; + } else { + TinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath()); + extractedFile.delete(); + } + } else { + extractedFile.getParentFile().mkdirs(); + } + + + String patchFileMd5 = info.patchMd5; + //it is a new file, just copy + ZipEntry patchFileEntry = patch.getEntry(patchRealPath); + + if (patchFileEntry == null) { + TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type, isUpgradePatch); + return false; + } + + if (patchFileMd5.equals("0")) { + if (!extract(patch, patchFileEntry, extractedFile, fileMd5, false)) { + TinkerLog.w(TAG, "Failed to extract file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type, isUpgradePatch); + return false; + } + } else { + //we do not check the intermediate files' md5 to save time, use check whether it is 32 length + if (!SharePatchFileUtil.checkIfMd5Valid(patchFileMd5)) { + TinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, patchFileMd5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + + ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath); + + if (rawApkFileEntry == null) { + TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type, isUpgradePatch); + return false; + } + + String rawApkCrc = info.rawCrc; + + //check source crc instead of md5 for faster + String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); + if (!rawEntryCrc.equals(rawApkCrc)) { + TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, rawApkCrc, rawEntryCrc); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type, isUpgradePatch); + return false; + } + InputStream oldStream = null; + InputStream newStream = null; + try { + oldStream = apk.getInputStream(rawApkFileEntry); + newStream = patch.getInputStream(patchFileEntry); + BSPatch.patchFast(oldStream, newStream, extractedFile); + } finally { + SharePatchFileUtil.closeQuietly(oldStream); + SharePatchFileUtil.closeQuietly(newStream); + } + + //go go go bsdiff get the + if (!SharePatchFileUtil.verifyFileMd5(extractedFile, fileMd5)) { + TinkerLog.w(TAG, "Failed to recover diff file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type, isUpgradePatch); + SharePatchFileUtil.safeDeleteFile(extractedFile); + return false; + } + TinkerLog.w(TAG, "success recover bsdiff file: %s, use time: %d", + extractedFile.getPath(), (System.currentTimeMillis() - start)); + } + } + + } catch (Throwable e) { +// e.printStackTrace(); + throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); + } finally { + SharePatchFileUtil.closeZip(apk); + SharePatchFileUtil.closeZip(patch); + } + return true; + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java new file mode 100644 index 00000000..936b50a6 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java @@ -0,0 +1,418 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.SystemClock; + +import com.tencent.tinker.commons.dexpatcher.DexPatchApplier; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareDexDiffPatchInfo; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import dalvik.system.DexFile; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Created by zhangshaowen on 16/4/12. + */ +public class DexDiffPatchInternal extends BasePatchInternal { + protected static final String TAG = "Tinker.DexDiffPatchInternal"; + + protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context, + String patchVersionDirectory, File patchFile, boolean isUpgradePatch) { + + if (!manager.isEnabledForDex()) { + TinkerLog.w(TAG, "patch recover, dex is not enabled"); + return true; + } + String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE); + + if (dexMeta == null) { + TinkerLog.w(TAG, "patch recover, dex is not contained"); + return true; + } + + long begin = SystemClock.elapsedRealtime(); + boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, isUpgradePatch); + long cost = SystemClock.elapsedRealtime() - begin; + TinkerLog.i(TAG, "recover dex result:%b, cost:%d, isUpgradePatch:%b", result, cost, isUpgradePatch); + return result; + } + + private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, File patchFile, boolean isUpgradePatch) { + String dir = patchVersionDirectory + "/" + DEX_PATH + "/"; + + int dexType = ShareTinkerInternals.isVmArt() ? TYPE_DEX_FOR_ART : TYPE_DEX; + if (!extractDexDiffInternals(context, dir, meta, patchFile, dexType, isUpgradePatch)) { + TinkerLog.w(TAG, "patch recover, extractDiffInternals fail"); + return false; + } + + Tinker manager = Tinker.with(context); + + File dexFiles = new File(dir); + File[] files = dexFiles.listFiles(); + + if (files != null) { + String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/"; + File optimizeDexDirectoryFile = new File(optimizeDexDirectory); + + if (!optimizeDexDirectoryFile.exists()) { + optimizeDexDirectoryFile.mkdirs(); + } + + for (File file : files) { + try { + String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile); + long start = System.currentTimeMillis(); + DexFile.loadDex(file.getAbsolutePath(), outputPathName, 0); + TinkerLog.i(TAG, "success dex optimize file, path: %s, use time: %d", file.getPath(), (System.currentTimeMillis() - start)); + } catch (Throwable e) { + TinkerLog.e(TAG, "dex optimize or load failed, path:" + file.getPath()); + //delete file + SharePatchFileUtil.safeDeleteFile(file); + manager.getPatchReporter().onPatchDexOptFail(patchFile, file, optimizeDexDirectory, file.getName(), e, isUpgradePatch); + return false; + } + } + } + + return true; + } + + + private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type, boolean isUpgradePatch) { + //parse + ArrayList patchList = new ArrayList<>(); + + ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList); + + if (patchList.isEmpty()) { + TinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type)); + return true; + } + + File directory = new File(dir); + if (!directory.exists()) { + directory.mkdirs(); + } + //I think it is better to extract the raw files from apk + Tinker manager = Tinker.with(context); + ZipFile apk = null; + ZipFile patch = null; + try { + ApplicationInfo applicationInfo = context.getApplicationInfo(); + if (applicationInfo == null) { + // Looks like running on a test Context, so just return without patching. + TinkerLog.w(TAG, "applicationInfo == null!!!!"); + return false; + } + String apkPath = applicationInfo.sourceDir; + apk = new ZipFile(apkPath); + patch = new ZipFile(patchFile); + + SmallPatchedDexItemFile smallPatchInfoFile = null; + + if (ShareTinkerInternals.isVmArt()) { + File extractedFile = new File(dir + ShareConstants.DEX_SMALLPATCH_INFO_FILE); + ZipEntry smallPatchInfoEntry = patch.getEntry(ShareConstants.DEX_SMALLPATCH_INFO_FILE); + if (smallPatchInfoEntry == null) { + TinkerLog.w(TAG, "small patch info is not exists, bad patch package?"); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, ShareConstants.DEX_SMALLPATCH_INFO_FILE, type, isUpgradePatch); + return false; + } + InputStream smallPatchInfoIs = null; + try { + smallPatchInfoIs = patch.getInputStream(smallPatchInfoEntry); + smallPatchInfoFile = new SmallPatchedDexItemFile(smallPatchInfoIs); + } catch (Throwable e) { + TinkerLog.w(TAG, "failed to read small patched info. reason: " + e.getMessage()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, ShareConstants.DEX_SMALLPATCH_INFO_FILE, type, isUpgradePatch); + return false; + } finally { + SharePatchFileUtil.closeQuietly(smallPatchInfoIs); + } + } + + for (ShareDexDiffPatchInfo info : patchList) { + long start = System.currentTimeMillis(); + + final String infoPath = info.path; + String patchRealPath; + if (infoPath.equals("")) { + patchRealPath = info.rawName; + } else { + patchRealPath = info.path + "/" + info.rawName; + } + + String dexDiffMd5 = info.dexDiffMd5; + String oldDexCrc = info.oldDexCrC; + + String extractedFileMd5 = ShareTinkerInternals.isVmArt() ? info.destMd5InArt : info.destMd5InDvm; + + if (!SharePatchFileUtil.checkIfMd5Valid(extractedFileMd5)) { + TinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + + File extractedFile = new File(dir + info.realName); + + //check file whether already exist + if (extractedFile.exists()) { + if (SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { + //it is ok, just continue + TinkerLog.w(TAG, "dex file %s is already exist, and md5 match, just continue", extractedFile.getPath()); + continue; + } else { + TinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath()); + extractedFile.delete(); + } + } else { + extractedFile.getParentFile().mkdirs(); + } + + ZipEntry patchFileEntry = patch.getEntry(patchRealPath); + ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath); + + if (oldDexCrc.equals("0")) { + if (patchFileEntry == null) { + TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + + //it is a new file, but maybe we need to repack the dex file + if (!extractDexFile(patch, patchFileEntry, extractedFile, info)) { + TinkerLog.w(TAG, "Failed to extract raw patch file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + } else if (dexDiffMd5.equals("0")) { + // skip process old dex for dalvik vm + if (!ShareTinkerInternals.isVmArt()) { + continue; + } + + if (rawApkFileEntry == null) { + TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + + //check source crc instead of md5 for faster + String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); + if (!rawEntryCrc.equals(oldDexCrc)) { + TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + + InputStream oldDexIs = null; + try { + oldDexIs = apk.getInputStream(rawApkFileEntry); + new DexPatchApplier(oldDexIs, (int) rawApkFileEntry.getSize(), null, smallPatchInfoFile).executeAndSaveTo(extractedFile); + } catch (Throwable e) { + TinkerLog.w(TAG, "Failed to recover dex file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + SharePatchFileUtil.safeDeleteFile(extractedFile); + return false; + } finally { + SharePatchFileUtil.closeQuietly(oldDexIs); + } + + if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { + TinkerLog.w(TAG, "Failed to recover dex file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + SharePatchFileUtil.safeDeleteFile(extractedFile); + return false; + } + } else { + if (patchFileEntry == null) { + TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + + if (!SharePatchFileUtil.checkIfMd5Valid(dexDiffMd5)) { + TinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, dexDiffMd5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + + if (rawApkFileEntry == null) { + TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + //check source crc instead of md5 for faster + String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); + if (!rawEntryCrc.equals(oldDexCrc)) { + TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + return false; + } + + final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(info.rawName); + InputStream oldInputStream = apk.getInputStream(rawApkFileEntry); + InputStream newInputStream = patch.getInputStream(patchFileEntry); + //if it is not the dex file or we are using jar mode, we should repack the output dex to jar + try { + if (!isRawDexFile || info.isJarMode) { + FileOutputStream fos = new FileOutputStream(extractedFile); + ZipOutputStream zos = new ZipOutputStream(new + BufferedOutputStream(fos)); + + try { + zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR)); + //it is not a raw dex file, we do not want to any temp files + int oldDexSize; + if (!isRawDexFile) { + ZipEntry entry; + ZipInputStream zis = new ZipInputStream(oldInputStream); + while ((entry = zis.getNextEntry()) != null) { + if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break; + } + if (entry == null) { + throw new TinkerRuntimeException("can't recognize zip dex format file:" + extractedFile.getAbsolutePath()); + } + oldInputStream = zis; + oldDexSize = (int) entry.getSize(); + } else { + oldDexSize = (int) rawApkFileEntry.getSize(); + } + new DexPatchApplier(oldInputStream, oldDexSize, newInputStream, smallPatchInfoFile).executeAndSaveTo(zos); + zos.closeEntry(); + } finally { + SharePatchFileUtil.closeQuietly(zos); + } + + } else { + new DexPatchApplier(oldInputStream, (int) rawApkFileEntry.getSize(), newInputStream, smallPatchInfoFile).executeAndSaveTo(extractedFile); + } + } finally { + SharePatchFileUtil.closeQuietly(oldInputStream); + SharePatchFileUtil.closeQuietly(newInputStream); + } + + if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { + TinkerLog.w(TAG, "Failed to recover dex file " + extractedFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type, isUpgradePatch); + SharePatchFileUtil.safeDeleteFile(extractedFile); + return false; + } + TinkerLog.w(TAG, "success recover dex file: %s, use time: %d", + extractedFile.getPath(), (System.currentTimeMillis() - start)); + } + } + + } catch (Throwable e) { +// e.printStackTrace(); + throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); + } finally { + SharePatchFileUtil.closeZip(apk); + SharePatchFileUtil.closeZip(patch); + } + return true; + } + + /** + * repack dex to jar + * + * @param zipFile + * @param entryFile + * @param extractTo + * @param targetMd5 + * @return boolean + * @throws IOException + */ + private static boolean extractDexToJar(ZipFile zipFile, ZipEntry entryFile, File extractTo, String targetMd5) throws IOException { + int numAttempts = 0; + boolean isExtractionSuccessful = false; + while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { + numAttempts++; + + FileOutputStream fos = new FileOutputStream(extractTo); + InputStream in = zipFile.getInputStream(entryFile); + + ZipOutputStream zos = null; + BufferedInputStream bis = null; + + TinkerLog.i(TAG, "try Extracting " + extractTo.getPath()); + try { + zos = new ZipOutputStream(new + BufferedOutputStream(fos)); + bis = new BufferedInputStream(in); + + byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; + ZipEntry entry = new ZipEntry(ShareConstants.DEX_IN_JAR); + zos.putNextEntry(entry); + int length = bis.read(buffer); + while (length != -1) { + zos.write(buffer, 0, length); + length = bis.read(buffer); + } + zos.closeEntry(); + } finally { + SharePatchFileUtil.closeQuietly(bis); + SharePatchFileUtil.closeQuietly(zos); + } + + isExtractionSuccessful = SharePatchFileUtil.verifyDexFileMd5(extractTo, targetMd5); + TinkerLog.i(TAG, "isExtractionSuccessful: %b", isExtractionSuccessful); + + if (!isExtractionSuccessful) { + extractTo.delete(); + if (extractTo.exists()) { + TinkerLog.e(TAG, "Failed to delete corrupted dex " + extractTo.getPath()); + } + } + } + return isExtractionSuccessful; + } + + private static boolean extractDexFile(ZipFile zipFile, ZipEntry entryFile, File extractTo, ShareDexDiffPatchInfo dexInfo) throws IOException { + final String fileMd5 = ShareTinkerInternals.isVmArt() ? dexInfo.destMd5InArt : dexInfo.destMd5InDvm; + final String rawName = dexInfo.rawName; + final boolean isJarMode = dexInfo.isJarMode; + //it is raw dex and we use jar mode, so we need to zip it! + if (SharePatchFileUtil.isRawDexFile(rawName) && isJarMode) { + return extractDexToJar(zipFile, entryFile, extractTo, fileMd5); + } + return extract(zipFile, entryFile, extractTo, fileMd5, true); + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/RepairPatch.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/RepairPatch.java new file mode 100644 index 00000000..f9910f82 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/RepairPatch.java @@ -0,0 +1,121 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; + +import com.tencent.tinker.lib.service.PatchResult; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/18. + * + * if some of a patch data(such as so, dex) is deleted, + * we will try to repair them via RepairPatch + * you can implement your own patch processor class + */ +public class RepairPatch extends AbstractPatch { + private static final String TAG = "Tinker.RepairPatch"; + + @Override + public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) { + + Tinker manager = Tinker.with(context); + + final File patchFile = new File(tempPatchPath); + + if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) { + TinkerLog.e(TAG, "RepairPatch tryPatch:patch is disabled, just return"); + return false; + } + + if (!patchFile.isFile() || !patchFile.exists()) { + TinkerLog.e(TAG, "RepairPatch tryPatch:patch file is not found, just return"); + return false; + } + //check the signature, we should create a new checker + ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context); + + + int returnCode = ShareTinkerInternals.checkSignatureAndTinkerID(context, patchFile, signatureCheck); + if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) { + TinkerLog.e(TAG, "RepairPatch tryPatch:onPatchPackageCheckFail"); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, false, returnCode); + return false; + } + + patchResult.patchTinkerID = signatureCheck.getNewTinkerID(); + patchResult.baseTinkerID = signatureCheck.getTinkerID(); + + //it is a old patch, so we should find a exist + SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo; + String patchMd5 = SharePatchFileUtil.getMD5(patchFile); + + //use md5 as version + patchResult.patchVersion = patchMd5; + + if (oldInfo == null) { + TinkerLog.e(TAG, "OldPatchProcessor tryPatch:onPatchVersionCheckFail, oldInfo is null"); + manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5, false); + return false; + } else { + if (oldInfo.oldVersion == null || oldInfo.newVersion == null) { + TinkerLog.e(TAG, "RepairPatch tryPatch:onPatchInfoCorrupted"); + manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion, false); + return false; + } + //already have patch + if (!oldInfo.oldVersion.equals(patchMd5) || !oldInfo.newVersion.equals(patchMd5)) { + TinkerLog.e(TAG, "RepairPatch tryPatch:onPatchVersionCheckFail"); + manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5, false); + return false; + } + } + + //check ok + final String patchDirectory = manager.getPatchDirectory().getAbsolutePath(); + + final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); + + final String patchVersionDirectory = patchDirectory + "/" + patchName; + + if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, patchFile, false)) { + TinkerLog.e(TAG, "RepairPatch tryPatch:try patch dex failed"); + return false; + } + + if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, patchFile, false)) { + TinkerLog.e(TAG, "RepairPatch tryPatch:try patch library failed"); + return false; + } + + if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, patchFile, false)) { + TinkerLog.e(TAG, "RepairPatch tryPatch:try patch resource failed"); + return false; + } + return true; + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ResDiffPatchInternal.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ResDiffPatchInternal.java new file mode 100644 index 00000000..21ac8d85 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ResDiffPatchInternal.java @@ -0,0 +1,317 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.SystemClock; + +import com.tencent.tinker.bsdiff.BSPatch; +import com.tencent.tinker.commons.resutil.ResUtil; +import com.tencent.tinker.commons.ziputil.TinkerZipEntry; +import com.tencent.tinker.commons.ziputil.TinkerZipFile; +import com.tencent.tinker.commons.ziputil.TinkerZipOutputStream; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareResPatchInfo; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Created by zhangshaowen on 2016/8/8. + */ +public class ResDiffPatchInternal extends BasePatchInternal { + + protected static final String TAG = "Tinker.ResDiffPatchInternal"; + + protected static boolean tryRecoverResourceFiles(Tinker manager, ShareSecurityCheck checker, Context context, + String patchVersionDirectory, File patchFile, boolean isUpgradePatch) { + + if (!manager.isEnabledForResource()) { + TinkerLog.w(TAG, "patch recover, resource is not enabled"); + return true; + } + String resourceMeta = checker.getMetaContentMap().get(RES_META_FILE); + + if (resourceMeta == null || resourceMeta.length() == 0) { + TinkerLog.w(TAG, "patch recover, resource is not contained"); + return true; + } + + long begin = SystemClock.elapsedRealtime(); + boolean result = patchResourceExtractViaResourceDiff(context, patchVersionDirectory, resourceMeta, patchFile, isUpgradePatch); + long cost = SystemClock.elapsedRealtime() - begin; + TinkerLog.i(TAG, "recover resource result:%b, cost:%d, isNewPatch:%b", result, cost, isUpgradePatch); + return result; + } + + private static boolean patchResourceExtractViaResourceDiff(Context context, String patchVersionDirectory, String meta, File patchFile, boolean isUpgradePatch) { + String dir = patchVersionDirectory + "/" + ShareConstants.RES_PATH + "/"; + + if (!extractResourceDiffInternals(context, dir, meta, patchFile, TYPE_RESOURCE, isUpgradePatch)) { + TinkerLog.w(TAG, "patch recover, extractDiffInternals fail"); + return false; + } + return true; + } + + private static boolean extractResourceDiffInternals(Context context, String dir, String meta, File patchFile, int type, boolean isUpgradePatch) { + ShareResPatchInfo resPatchInfo = new ShareResPatchInfo(); + ShareResPatchInfo.parseAllResPatchInfo(meta, resPatchInfo); + TinkerLog.i(TAG, "res dir: %s, meta: %s", dir, resPatchInfo.toString()); + Tinker manager = Tinker.with(context); + + if (!SharePatchFileUtil.checkIfMd5Valid(resPatchInfo.resArscMd5)) { + TinkerLog.w(TAG, "resource meta file md5 mismatch, type:%s, md5: %s", ShareTinkerInternals.getTypeString(type), resPatchInfo.resArscMd5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + File directory = new File(dir); + + File resOutput = new File(directory, ShareConstants.RES_NAME); + //check result file whether already exist + if (resOutput.exists()) { + if (SharePatchFileUtil.checkResourceArscMd5(resOutput, resPatchInfo.resArscMd5)) { + //it is ok, just continue + TinkerLog.w(TAG, "resource file %s is already exist, and md5 match, just return true", resOutput.getPath()); + return true; + } else { + TinkerLog.w(TAG, "have a mismatch corrupted resource " + resOutput.getPath()); + resOutput.delete(); + } + } else { + resOutput.getParentFile().mkdirs(); + } + + try { + ApplicationInfo applicationInfo = context.getApplicationInfo(); + if (applicationInfo == null) { + //Looks like running on a test Context, so just return without patching. + TinkerLog.w(TAG, "applicationInfo == null!!!!"); + return false; + } + String apkPath = applicationInfo.sourceDir; + + if (!checkAndExtractResourceLargeFile(context, apkPath, directory, patchFile, resPatchInfo, type, isUpgradePatch)) { + return false; + } + + TinkerZipOutputStream out = null; + TinkerZipFile oldApk = null; + TinkerZipFile newApk = null; + int totalEntryCount = 0; + try { + out = new TinkerZipOutputStream(new BufferedOutputStream(new FileOutputStream(resOutput))); + oldApk = new TinkerZipFile(apkPath); + newApk = new TinkerZipFile(patchFile); + final Enumeration entries = oldApk.entries(); + while (entries.hasMoreElements()) { + TinkerZipEntry zipEntry = entries.nextElement(); + if (zipEntry == null) { + throw new TinkerRuntimeException("zipEntry is null when get from oldApk"); + } + String name = zipEntry.getName(); + if (ShareResPatchInfo.checkFileInPattern(resPatchInfo.patterns, name)) { + //won't contain in add set. + if (!resPatchInfo.deleteRes.contains(name) + && !resPatchInfo.modRes.contains(name) + && !resPatchInfo.largeModRes.contains(name) + && !name.equals(ShareConstants.RES_MANIFEST)) { + ResUtil.extractTinkerEntry(oldApk, zipEntry, out); + totalEntryCount++; + } + } + } + + //process manifest + TinkerZipEntry manifestZipEntry = oldApk.getEntry(ShareConstants.RES_MANIFEST); + if (manifestZipEntry == null) { + TinkerLog.w(TAG, "manifest patch entry is null. path:" + ShareConstants.RES_MANIFEST); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, ShareConstants.RES_MANIFEST, type, isUpgradePatch); + return false; + } + ResUtil.extractTinkerEntry(oldApk, manifestZipEntry, out); + totalEntryCount++; + + for (String name : resPatchInfo.largeModRes) { + TinkerZipEntry largeZipEntry = oldApk.getEntry(name); + if (largeZipEntry == null) { + TinkerLog.w(TAG, "large patch entry is null. path:" + name); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type, isUpgradePatch); + return false; + } + ShareResPatchInfo.LargeModeInfo largeModeInfo = resPatchInfo.largeModMap.get(name); + ResUtil.extractLargeModifyFile(largeZipEntry, largeModeInfo.file, largeModeInfo.crc, out); + totalEntryCount++; + } + + for (String name : resPatchInfo.addRes) { + TinkerZipEntry addZipEntry = newApk.getEntry(name); + if (addZipEntry == null) { + TinkerLog.w(TAG, "add patch entry is null. path:" + name); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type, isUpgradePatch); + return false; + } + ResUtil.extractTinkerEntry(newApk, addZipEntry, out); + totalEntryCount++; + } + + for (String name : resPatchInfo.modRes) { + TinkerZipEntry modZipEntry = newApk.getEntry(name); + if (modZipEntry == null) { + TinkerLog.w(TAG, "mod patch entry is null. path:" + name); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type, isUpgradePatch); + return false; + } + ResUtil.extractTinkerEntry(newApk, modZipEntry, out); + totalEntryCount++; + } + } finally { + if (out != null) { + out.close(); + } + if (oldApk != null) { + oldApk.close(); + } + if (newApk != null) { + newApk.close(); + } + //delete temp files + for (ShareResPatchInfo.LargeModeInfo largeModeInfo : resPatchInfo.largeModMap.values()) { + SharePatchFileUtil.safeDeleteFile(largeModeInfo.file); + } + } + boolean result = SharePatchFileUtil.checkResourceArscMd5(resOutput, resPatchInfo.resArscMd5); + + if (!result) { + TinkerLog.i(TAG, "check final new resource file fail path:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length()); + SharePatchFileUtil.safeDeleteFile(resOutput); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, ShareConstants.RES_NAME, type, isUpgradePatch); + return false; + } + + TinkerLog.i(TAG, "final new resource file:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length()); + } catch (Throwable e) { +// e.printStackTrace(); + throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); + } + return true; + } + + private static boolean checkAndExtractResourceLargeFile(Context context, String apkPath, File directory, + File patchFile, ShareResPatchInfo resPatchInfo, int type, boolean isUpgradePatch) { + long start = System.currentTimeMillis(); + Tinker manager = Tinker.with(context); + ZipFile apkFile = null; + ZipFile patchZipFile = null; + try { + //recover resources.arsc first + apkFile = new ZipFile(apkPath); + ZipEntry arscEntry = apkFile.getEntry(ShareConstants.RES_ARSC); + File arscFile = new File(directory, ShareConstants.RES_ARSC); + if (arscEntry == null) { + TinkerLog.w(TAG, "resources apk entry is null. path:" + ShareConstants.RES_ARSC); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, arscFile, ShareConstants.RES_ARSC, type, isUpgradePatch); + return false; + } + //use base resources.arsc crc to identify base.apk + String baseArscCrc = String.valueOf(arscEntry.getCrc()); + if (!baseArscCrc.equals(resPatchInfo.arscBaseCrc)) { + TinkerLog.e(TAG, "resources.arsc's crc is not equal, expect crc: %s, got crc: %s", resPatchInfo.arscBaseCrc, baseArscCrc); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, arscFile, ShareConstants.RES_ARSC, type, isUpgradePatch); + return false; + } + + //resource arsc is not changed, just return true + if (resPatchInfo.largeModRes.isEmpty()) { + TinkerLog.i(TAG, "no large modify resources, just return"); + return true; + } + for (String name : resPatchInfo.largeModRes) { + long largeStart = System.currentTimeMillis(); + ShareResPatchInfo.LargeModeInfo largeModeInfo = resPatchInfo.largeModMap.get(name); + + if (largeModeInfo == null) { + TinkerLog.w(TAG, "resource not found largeModeInfo, type:%s, name: %s", ShareTinkerInternals.getTypeString(type), name); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + + largeModeInfo.file = new File(directory, name); + SharePatchFileUtil.ensureFileDirectory(largeModeInfo.file); + + //we do not check the intermediate files' md5 to save time, use check whether it is 32 length + if (!SharePatchFileUtil.checkIfMd5Valid(largeModeInfo.md5)) { + TinkerLog.w(TAG, "resource meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), name, largeModeInfo.md5); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, isUpgradePatch, BasePatchInternal.getMetaCorruptedCode(type)); + return false; + } + patchZipFile = new ZipFile(patchFile); + ZipEntry patchEntry = patchZipFile.getEntry(name); + if (patchEntry == null) { + TinkerLog.w(TAG, "large mod patch entry is null. path:" + name); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type, isUpgradePatch); + return false; + } + + ZipEntry baseEntry = apkFile.getEntry(name); + if (baseEntry == null) { + TinkerLog.w(TAG, "resources apk entry is null. path:" + name); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type, isUpgradePatch); + return false; + } + InputStream oldStream = null; + InputStream newStream = null; + try { + oldStream = apkFile.getInputStream(baseEntry); + newStream = patchZipFile.getInputStream(patchEntry); + BSPatch.patchFast(oldStream, newStream, largeModeInfo.file); + } finally { + SharePatchFileUtil.closeQuietly(oldStream); + SharePatchFileUtil.closeQuietly(newStream); + } + //go go go bsdiff get the + if (!SharePatchFileUtil.verifyFileMd5(largeModeInfo.file, largeModeInfo.md5)) { + TinkerLog.w(TAG, "Failed to recover large modify file:%s", largeModeInfo.file.getPath()); + SharePatchFileUtil.safeDeleteFile(largeModeInfo.file); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type, isUpgradePatch); + return false; + } + TinkerLog.w(TAG, "success recover large modify file:%s , file size:%d, use time:%d", largeModeInfo.file.getPath(), largeModeInfo.file.length(), (System.currentTimeMillis() - largeStart)); + } + TinkerLog.w(TAG, "success recover all large modify use time:%d", (System.currentTimeMillis() - start)); + } catch (Throwable e) { +// e.printStackTrace(); + throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); + } finally { + SharePatchFileUtil.closeZip(apkFile); + SharePatchFileUtil.closeZip(patchZipFile); + } + return true; + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java new file mode 100644 index 00000000..3a91e09f --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java @@ -0,0 +1,158 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.patch; + +import android.content.Context; + +import com.tencent.tinker.lib.service.PatchResult; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; +import java.io.IOException; + + +/** + * generate new patch, you can implement your own patch processor class + * Created by zhangshaowen on 16/3/14. + */ +public class UpgradePatch extends AbstractPatch { + private static final String TAG = "Tinker.UpgradePatch"; + + @Override + public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) { + Tinker manager = Tinker.with(context); + + final File patchFile = new File(tempPatchPath); + + if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return"); + return false; + } + + if (!patchFile.isFile() || !patchFile.exists()) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return"); + return false; + } + //check the signature, we should create a new checker + ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context); + + int returnCode = ShareTinkerInternals.checkSignatureAndTinkerID(context, patchFile, signatureCheck); + if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail"); + manager.getPatchReporter().onPatchPackageCheckFail(patchFile, true, returnCode); + return false; + } + + patchResult.patchTinkerID = signatureCheck.getNewTinkerID(); + patchResult.baseTinkerID = signatureCheck.getTinkerID(); + + //it is a new patch, so we should not find a exist + SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo; + String patchMd5 = SharePatchFileUtil.getMD5(patchFile); + + if (patchMd5 == null) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return"); + return false; + } + + //use md5 as version + patchResult.patchVersion = patchMd5; + + SharePatchInfo newInfo; + + //already have patch + if (oldInfo != null) { + if (oldInfo.oldVersion == null || oldInfo.newVersion == null) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted"); + manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion, true); + return false; + } + + if (oldInfo.oldVersion.equals(patchMd5) || oldInfo.newVersion.equals(patchMd5)) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail"); + manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5, true); + return false; + } + newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5); + } else { + newInfo = new SharePatchInfo("", patchMd5); + } + + //check ok, we can real recover a new patch + final String patchDirectory = manager.getPatchDirectory().getAbsolutePath(); + + TinkerLog.i(TAG, "UpgradePatch tryPatch:dexDiffMd5:%s", patchMd5); + + final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); + + final String patchVersionDirectory = patchDirectory + "/" + patchName; + + TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory); + + //it is a new patch, we first delete if there is any files + //don't delete dir for faster retry +// SharePatchFileUtil.deleteDir(patchVersionDirectory); + + //copy file + File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5)); + try { + SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile); + TinkerLog.w(TAG, "UpgradePatch after %s size:%d, %s size:%d", patchFile.getAbsolutePath(), patchFile.length(), + destPatchFile.getAbsolutePath(), destPatchFile.length()); + } catch (IOException e) { +// e.printStackTrace(); + TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath()); + manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE, true); + return false; + } + + //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process + if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, true)) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed"); + return false; + } + + if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, true)) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed"); + return false; + } + + if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, true)) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed"); + return false; + } + + final File patchInfoFile = manager.getPatchInfoFile(); + + if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, SharePatchFileUtil.getPatchInfoLockFile(patchDirectory))) { + TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed"); + manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion, true); + return false; + } + + + TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok"); + return true; + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultLoadReporter.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultLoadReporter.java new file mode 100644 index 00000000..f955477d --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultLoadReporter.java @@ -0,0 +1,255 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.reporter; + + +import android.content.Context; + +import com.tencent.tinker.lib.service.TinkerPatchService; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/10. + * the default implement for LoadReporter + * you can extent it for your own work + * all is running in the process which loading the patch + */ +public class DefaultLoadReporter implements LoadReporter { + private static final String TAG = "Tinker.DefaultLoadReporter"; + protected final Context context; + + public DefaultLoadReporter(Context context) { + this.context = context; + } + + /** + * we receive a patch, but it check fails by PatchListener + * so we would not start a {@link TinkerPatchService} + * + * @param patchFile + * @param errorCode errorCode define as following + * {@code ShareConstants.ERROR_PATCH_OK} it is ok + * {@code ShareConstants.ERROR_PATCH_DISABLE} patch is disable + * {@code ShareConstants.ERROR_PATCH_NOTEXIST} the file of tempPatchPatch file is not exist + * {@code ShareConstants.ERROR_PATCH_RUNNING} the recover service is running now, try later + * {@code ShareConstants.ERROR_PATCH_INSERVICE} the recover service can't send patch request + * + * @param isUpgrade whether is a new patch, or just recover the old patch + */ + @Override + public void onLoadPatchListenerReceiveFail(File patchFile, int errorCode, boolean isUpgrade) { + TinkerLog.i(TAG, "patch load Reporter: patch receive fail:%s, code:%d, isUpgrade:%b", patchFile.getAbsolutePath(), errorCode, isUpgrade); + } + + + /** + * we can only handle patch version change in the main process, + * we will need to kill all other process to ensure that all process's code is the same. + * you can delete the old patch version file as {@link DefaultLoadReporter#onLoadPatchVersionChanged(String, String, File, String)} + * or you can restart your other process here + * + * @param oldVersion + * @param newVersion + * @param patchDirectoryFile + * @param currentPatchName + */ + @Override + public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) { + TinkerLog.i(TAG, "patch version change from " + oldVersion + " to " + newVersion); + + if (oldVersion == null || newVersion == null) { + return; + } + if (oldVersion.equals(newVersion)) { + return; + } + + //check main process + if (!Tinker.with(context).isMainProcess()) { + return; + } + TinkerLog.i(TAG, "try kill all other process"); + //kill all other process to ensure that all process's code is the same. + ShareTinkerInternals.killAllOtherProcess(context); + + //delete old patch files + File[] files = patchDirectoryFile.listFiles(); + if (files != null) { + for (File file : files) { + String name = file.getName(); + if (file.isDirectory() && !name.equals(currentPatchName)) { + SharePatchFileUtil.deleteDir(file); + } + } + } + } + + /** + * some files is not found, + * we'd like to recover the old patch with {@link TinkerPatchService} in OldPatchProcessor mode + * as {@link DefaultLoadReporter#onLoadFileNotFound(File, int, boolean)} + * + * @param file the missing file + * @param fileType file type as following + * {@code ShareConstants.TYPE_PATCH_FILE} patch file or directory not found + * {@code ShareConstants.TYPE_PATCH_INFO} patch info file or directory not found + * {@code ShareConstants.TYPE_DEX} patch dex file or directory not found + * {@code ShareConstants.TYPE_LIBRARY} patch lib file or directory not found + * {@code ShareConstants.TYPE_RESOURCE} patch lib file or directory not found + * + * @param isDirectory whether is directory for the file type + */ + @Override + public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) { + TinkerLog.i(TAG, "patch file not found: %s, fileType:%d, isDirectory:%b", file.getAbsolutePath(), fileType, isDirectory); + if (fileType == ShareConstants.TYPE_DEX || fileType == ShareConstants.TYPE_DEX_OPT + || fileType == ShareConstants.TYPE_LIBRARY || fileType == ShareConstants.TYPE_RESOURCE) { + Tinker tinker = Tinker.with(context); + + //we can recover at any process except recover process + if (!tinker.isPatchProcess()) { + File patchVersionFile = tinker.getTinkerLoadResultIfPresent().patchVersionFile; + if (patchVersionFile != null) { + TinkerInstaller.onReceiveRepairPatch(context, patchVersionFile.getAbsolutePath()); + } + } + } else if (fileType == ShareConstants.TYPE_PATCH_FILE || fileType == ShareConstants.TYPE_PATCH_INFO) { + Tinker.with(context).cleanPatch(); + } + } + + /** + * default, we don't check file's md5 when we load them. but you can set {@code TinkerApplication.tinkerLoadVerifyFlag} + * with tinker-android-anno, you can set {@code DefaultLifeCycle.loadVerifyFlag} + * some files' md5 is mismatch with the meta.txt file + * we won't load these files, clean patch for safety + * + * @param file the mismatch file + * @param fileType file type, just now, only dex or library will go here + * {@code ShareConstants.TYPE_DEX} patch dex file md5 mismatch + * {@code ShareConstants.TYPE_LIBRARY} patch lib file md5 mismatch + * {@code ShareConstants.TYPE_RESOURCE} patch resource file md5 mismatch + */ + @Override + public void onLoadFileMd5Mismatch(File file, int fileType) { + TinkerLog.i(TAG, "patch file md5 mismatch file: %s, fileType:%d", file.getAbsolutePath(), fileType); + //clean patch for safety + Tinker.with(context).cleanPatch(); + } + + /** + * when we load a new patch, we need to rewrite the patch.info file. + * but patch info corrupted, we can't recover from it + * we can clean patch as {@link DefaultLoadReporter#onLoadPatchInfoCorrupted(String, String, File)} + * + * @param oldVersion @nullable + * @param newVersion @nullable + * @param patchInfoFile + */ + @Override + public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) { + TinkerLog.i(TAG, "patch info file damage: %s", patchInfoFile.getAbsolutePath()); + TinkerLog.i(TAG, "patch info file damage from version: %s to version: %s", oldVersion, newVersion); + + Tinker.with(context).cleanPatch(); + } + + /** + * the load patch process is end, we can see the cost times and the return code + * return codes are define in {@link com.tencent.tinker.loader.shareutil.ShareConstants} + * + * @param patchDirectory the root patch directory {you_apk_data}/tinker + * @param loadCode {@code ShareConstants.ERROR_LOAD_OK}, 0 means success + * @param cost time in ms + */ + @Override + public void onLoadResult(File patchDirectory, int loadCode, long cost) { + TinkerLog.i(TAG, "patch load result, path:%s, code:%d, cost:%d", patchDirectory.getAbsolutePath(), loadCode, cost); + //you can just report the result here + } + + /** + * load patch occur unknown exception that we have wrap try catch for you! + * you may need to report this exception and contact me + * welcome to report a new issues for us! + * you can disable patch as {@link DefaultLoadReporter#onLoadException(Throwable, int)} + * + * @param e + * @param errorCode exception code + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN} unknown exception + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_DEX} exception when load dex + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE} exception when load resource + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT} exception unCaught + */ + @Override + public void onLoadException(Throwable e, int errorCode) { + //for unCaught or dex exception, disable tinker all the time with sp + switch (errorCode) { + case ShareConstants.ERROR_LOAD_EXCEPTION_DEX: + if (e.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) { + TinkerLog.e(TAG, "tinker dex check fail:" + e.getMessage()); + } else { + TinkerLog.i(TAG, "patch load dex exception: %s", e); + } + ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); + TinkerLog.i(TAG, "dex exception disable tinker forever with sp"); + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE: + TinkerLog.i(TAG, "patch load resource exception: %s", e); + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT: + TinkerLog.i(TAG, "patch load unCatch exception: %s", e); + ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); + TinkerLog.i(TAG, "unCaught exception disable tinker forever with sp"); + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN: + TinkerLog.i(TAG, "patch load unknown exception: %s", e); + break; + } + TinkerLog.printErrStackTrace(TAG, e, "tinker load exception"); + + Tinker.with(context).setTinkerDisable(); + Tinker.with(context).cleanPatch(); + } + /** + * check patch signature, TINKER_ID and meta files + * + * @param patchFile the loading path file + * @param errorCode 0 is ok, you should define the errorCode yourself + * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok + * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found + * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file + * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal + * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail + */ + @Override + public void onLoadPackageCheckFail(File patchFile, int errorCode) { + TinkerLog.i(TAG, "load patch package check fail file path:%s, errorCode:%d", patchFile.getAbsolutePath(), errorCode); + Tinker.with(context).cleanPatch(); + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultPatchReporter.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultPatchReporter.java new file mode 100644 index 00000000..c1717c41 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultPatchReporter.java @@ -0,0 +1,188 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.reporter; + + +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.service.DefaultTinkerResultService; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/14. + * the default implement for PatchReporter + * you can extent it for your own work + * all is running in the :patch process + */ +public class DefaultPatchReporter implements PatchReporter { + private static final String TAG = "Tinker.DefaultPatchReporter"; + protected final Context context; + + public DefaultPatchReporter(Context context) { + this.context = context; + } + + /************************************ :patch process below ***************************************/ + /** + * use for report or some work at the beginning of TinkerPatchService + * {@code TinkerPatchService.onHandleIntent} begin + * + * @param intent + */ + @Override + public void onPatchServiceStart(Intent intent) { + TinkerLog.i(TAG, "patchReporter: patch service start"); + } + + /** + * check patch signature, TINKER_ID and meta files + * + * @param patchFile the loading path file + * @param errorCode 0 is ok, you should define the errorCode yourself + * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok + * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found + * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file + * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal + * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail + */ + @Override + public void onPatchPackageCheckFail(File patchFile, boolean isUpgradePatch, int errorCode) { + TinkerLog.i(TAG, "patchReporter: package check failed. path:%s, isUpgrade:%b, code:%d", patchFile.getAbsolutePath(), isUpgradePatch, errorCode); + //only meta corrupted, need to delete temp files. others is just in the check time! + if (errorCode == ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED + || errorCode == ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED + || errorCode == ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED) { + //delete temp files + Tinker.with(context).cleanPatchByVersion(patchFile); + } + } + + /** + * for upgrade patch, patchFileVersion can't equal oldVersion or newVersion in oldPatchInfo + * for repair patch, oldPatchInfo can 't be null, and patchFileVersion must equal with oldVersion and newVersion + * + * @param patchFile the input patch file to recover + * @param oldPatchInfo the current patch info + * @param patchFileVersion it is the md5 of the input patchFile + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: patch version exist. path:%s, version:%s, isUpgrade:%b", patchFile.getAbsolutePath(), patchFileVersion, isUpgradePatch); + //no need to delete temp files, because it is only in the check time! + } + + /** + * try to recover file fail + * + * @param patchFile the input patch file to recover + * @param extractTo the target file + * @param filename + * @param fileType file type as following + * {@code ShareConstants.TYPE_DEX} extract patch dex file fail + * {@code ShareConstants.TYPE_DEX_FOR_ART} extract patch small art dex file fail + * {@code ShareConstants.TYPE_LIBRARY} extract patch library fail + * {@code ShareConstants.TYPE_PATCH_FILE} copy patch file fail + * {@code ShareConstants.TYPE_RESOURCE} extract patch resource fail + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: file extract fail type:%s, path:%s, extractTo:%s, filename:%s, isUpgrade:%b", + ShareTinkerInternals.getTypeString(fileType), patchFile.getPath(), extractTo.getPath(), filename, isUpgradePatch); + //delete temp files + Tinker.with(context).cleanPatchByVersion(patchFile); + } + + /** + * dex opt failed + * + * @param patchFile the input patch file to recover + * @param dexFile the dex file + * @param optDirectory + * @param dexName dexName try to dexOpt + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchDexOptFail(File patchFile, File dexFile, String optDirectory, String dexName, Throwable t, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: dex opt fail path:%s, dexPath:%s, optDir:%s, dexName:%s, isUpgrade:%b", + patchFile.getAbsolutePath(), dexFile.getPath(), optDirectory, dexName, isUpgradePatch); + TinkerLog.printErrStackTrace(TAG, t, "onPatchDexOptFail:"); + //delete temp files + Tinker.with(context).cleanPatchByVersion(patchFile); + } + + /** + * recover result, we will also send a result to {@link DefaultTinkerResultService} + * + * @param patchFile the input patch file to recover + * @param success if it is success + * @param cost cost time in ms + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchResult(File patchFile, boolean success, long cost, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: patch all result path:%s, success:%b, cost:%d, isUpgrade:%b", patchFile.getAbsolutePath(), success, cost, isUpgradePatch); + //you can just report the result here + } + + /** + * when we load a new patch, we need to rewrite the patch.info file. + * but patch info corrupted, we can't recover from it + * + * @param patchFile the input patch file to recover + * @param oldVersion old patch version + * @param newVersion new patch version + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: patch info is corrupted. old:%s, new:%s, isUpgradeP:%b", oldVersion, newVersion, isUpgradePatch); + //patch.info is corrupted, just clean all patch + Tinker.with(context).cleanPatch(); + } + + /** + * recover patch occur unknown exception that we have wrap try catch for you! + * you may need to report this exception and contact me + * welcome to report a new issues for us! + * + * @param patchFile the input file to patch + * @param e + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + @Override + public void onPatchException(File patchFile, Throwable e, boolean isUpgradePatch) { + TinkerLog.i(TAG, "patchReporter: patch exception path:%s, throwable:%s, isUpgrade:%b", patchFile.getAbsolutePath(), e.getMessage(), isUpgradePatch); + TinkerLog.printErrStackTrace(TAG, e, "tinker patch exception"); + //don't accept request any more! + Tinker.with(context).setTinkerDisable(); + ////delete temp files, I think we don't have to clean all patch + Tinker.with(context).cleanPatchByVersion(patchFile); + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/LoadReporter.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/LoadReporter.java new file mode 100644 index 00000000..67f4c7aa --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/LoadReporter.java @@ -0,0 +1,140 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.reporter; + +import com.tencent.tinker.lib.service.TinkerPatchService; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/10. + */ +public interface LoadReporter { + + /** + * we receive a patch, but it check fails by PatchListener + * so we would not start a {@link TinkerPatchService} + * + * @param patchFile + * @param errorCode errorCode define as following + * {@code ShareConstants.ERROR_PATCH_OK} it is ok + * {@code ShareConstants.ERROR_PATCH_DISABLE} patch is disable + * {@code ShareConstants.ERROR_PATCH_NOTEXIST} the file of tempPatchPatch file is not exist + * {@code ShareConstants.ERROR_PATCH_RUNNING} the recover service is running now, try later + * {@code ShareConstants.ERROR_PATCH_INSERVICE} the recover service can't send patch request + * @param isUpgrade whether is a new patch, or just recover the old patch + */ + void onLoadPatchListenerReceiveFail(File patchFile, int errorCode, boolean isUpgrade); + + /** + * we can only handle patch version change in the main process, + * we will need to kill all other process to ensure that all process's code is the same. + * you can delete the old patch version file as {@link DefaultLoadReporter#onLoadPatchVersionChanged(String, String, File, String)} + * or you can restart your other process here + * + * @param oldVersion + * @param newVersion + * @param patchDirectoryFile + * @param currentPatchName + */ + void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName); + + /** + * the load patch process is end, we can see the cost times and the return code + * return codes are define in {@link com.tencent.tinker.loader.shareutil.ShareConstants} + * + * @param patchDirectory the root patch directory {you_apk_data}/tinker + * @param loadCode {@code ShareConstants.ERROR_LOAD_OK}, 0 means success + * @param cost time in MS + */ + void onLoadResult(File patchDirectory, int loadCode, long cost); + + /** + * load patch occur unknown exception that we have wrap try catch for you! + * you may need to report this exception and contact me + * welcome to report a new issues for us! + * you can disable patch as {@link DefaultLoadReporter#onLoadException(Throwable, int)} + * + * @param e + * @param errorCode exception code + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN} unknown exception + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_DEX} exception when load dex + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE} exception when load resource + * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT} exception unCaught + */ + void onLoadException(Throwable e, int errorCode); + + /** + * some files is not found, + * we'd like to recover the old patch with {@link TinkerPatchService} in OldPatchProcessor mode + * as {@link DefaultLoadReporter#onLoadFileNotFound(File, int, boolean)} + * + * @param file the missing file + * @param fileType file type as following + * {@code ShareConstants.TYPE_PATCH_FILE} patch file or directory not found + * {@code ShareConstants.TYPE_PATCH_INFO} patch info file or directory not found + * {@code ShareConstants.TYPE_DEX} patch dex file or directory not found + * {@code ShareConstants.TYPE_LIBRARY} patch lib file or directory not found + * {@code ShareConstants.TYPE_RESOURCE} patch lib file or directory not found + * + * @param isDirectory whether is directory for the file type + */ + void onLoadFileNotFound(File file, int fileType, boolean isDirectory); + + /** + * default, we don't check file's md5 when we load them. but you can set {@code TinkerApplication.tinkerLoadVerifyFlag} + * with tinker-android-anno, you can set {@code DefaultLifeCycle.loadVerifyFlag} + * some files' md5 is mismatch with the meta.txt file + * we won't load these files, clean patch for safety + * + * @param file the mismatch file + * @param fileType file type, just now, only dex or library will go here + * {@code ShareConstants.TYPE_DEX} patch dex file md5 mismatch + * {@code ShareConstants.TYPE_LIBRARY} patch lib file md5 mismatch + * {@code ShareConstants.TYPE_RESOURCE} patch resource file md5 mismatch + */ + void onLoadFileMd5Mismatch(File file, int fileType); + + /** + * when we load a new patch, we need to rewrite the patch.info file. + * but patch info corrupted, we can't recover from it + * we can clean patch as {@link DefaultLoadReporter#onLoadPatchInfoCorrupted(String, String, File)} + * + * @param oldVersion @nullable + * @param newVersion @nullable + * @param patchInfoFile + */ + void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile); + + /** + * check patch signature, TINKER_ID and meta files + * + * @param patchFile the loading path file + * @param errorCode 0 is ok, you should define the errorCode yourself + * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok + * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found + * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file + * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal + * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail + */ + void onLoadPackageCheckFail(File patchFile, int errorCode); + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/PatchReporter.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/PatchReporter.java new file mode 100644 index 00000000..dbc5860c --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/PatchReporter.java @@ -0,0 +1,139 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.reporter; + + +import android.content.Intent; + +import com.tencent.tinker.lib.patch.RepairPatch; +import com.tencent.tinker.lib.patch.UpgradePatch; +import com.tencent.tinker.lib.service.DefaultTinkerResultService; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/14. + * + * isUpgradePatch: + * true: means that it is a newly patch, we would default use {@link UpgradePatch} + * to do the job + * + * false: means that there are some files missing in current patch, we want to repair them, + * we would default use {@link RepairPatch} to do the recover patch job + */ +public interface PatchReporter { + + /** + * use for report or some work at the beginning of TinkerPatchService + * {@code TinkerPatchService.onHandleIntent} begin + * + * @param intent + */ + void onPatchServiceStart(Intent intent); + + /** + * check patch signature, TINKER_ID and meta files + * + * @param patchFile the loading path file + * @param errorCode 0 is ok, you should define the errorCode yourself + * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok + * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk + * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail + * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest + * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file + * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal + * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail + */ + void onPatchPackageCheckFail(File patchFile, boolean isUpgradePatch, int errorCode); + + /** + * for upgrade patch, patchFileVersion can't equal oldVersion or newVersion in oldPatchInfo + * for repair patch, oldPatchInfo can 't be null, and patchFileVersion must equal with oldVersion and newVersion + * + * @param patchFile the input patch file to recover + * @param oldPatchInfo the current patch info + * @param patchFileVersion it is the md5 of the input patchFile + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion, boolean isUpgradePatch); + + + /** + * try to recover file fail + * + * @param patchFile the input patch file to recover + * @param extractTo the target file + * @param filename + * @param fileType file type as following + * {@code ShareConstants.TYPE_DEX} extract patch dex file fail + * {@code ShareConstants.TYPE_DEX_FOR_ART} extract patch small art dex file fail + * {@code ShareConstants.TYPE_LIBRARY} extract patch library fail + * {@code ShareConstants.TYPE_PATCH_FILE} copy patch file fail + * {@code ShareConstants.TYPE_RESOURCE} extract patch resource fail + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType, boolean isUpgradePatch); + + + /** + * dex opt failed + * + * @param patchFile the input patch file to recover + * @param dexFile the dex file + * @param optDirectory + * @param dexName dexName try to dexOpt + * @param t throwable + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchDexOptFail(File patchFile, File dexFile, String optDirectory, String dexName, Throwable t, boolean isUpgradePatch); + + + /** + * recover result, we will also send a result to {@link DefaultTinkerResultService} + * + * @param patchFile the input patch file to recover + * @param success if it is success + * @param cost cost time in ms + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchResult(File patchFile, boolean success, long cost, boolean isUpgradePatch); + + /** + * recover patch occur unknown exception that we have wrap try catch for you! + * you may need to report this exception and contact me + * welcome to report a new issues for us! + * + * @param patchFile the input file to patch + * @param e + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchException(File patchFile, Throwable e, boolean isUpgradePatch); + + /** + * when we load a new patch, we need to rewrite the patch.info file. + * but patch info corrupted, we can't recover from it + * + * @param patchFile the input patch file to recover + * @param oldVersion old patch version + * @param newVersion new patch version + * @param isUpgradePatch whether it is a new patch file, or just recover some of the current patch files + */ + void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion, boolean isUpgradePatch); + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/AbstractResultService.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/AbstractResultService.java new file mode 100644 index 00000000..0ecb1bca --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/AbstractResultService.java @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.service; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; + +/** + * Created by zhangshaowen on 16/3/14. + */ +public abstract class AbstractResultService extends IntentService { + private static final String TAG = "Tinker.AbstractResultService"; + + private static final String RESULT_EXTRA = "result_extra"; + + private static Class resultServiceClass = null; + + /** + * Creates an IntentService. Invoked by your subclass's constructor. + */ + public AbstractResultService() { + super(AbstractResultService.class.getSimpleName()); + } + + public static void runResultService(Context context, PatchResult result) { + if (resultServiceClass == null) { + throw new TinkerRuntimeException("resultServiceClass is null."); + } + Intent intent = new Intent(context, resultServiceClass); + intent.putExtra(RESULT_EXTRA, result); + + context.startService(intent); + } + + public static void setResultServiceClass(Class serviceClass) { + resultServiceClass = serviceClass; + //try to load + try { + Class.forName(serviceClass.getName()); + } catch (ClassNotFoundException e) { +// e.printStackTrace(); + } + + } + + @Override + protected void onHandleIntent(Intent intent) { + if (intent == null) { + TinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring."); + return; + } + PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA); + + onPatchResult(result); + } + + public abstract void onPatchResult(PatchResult result); + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/DefaultTinkerResultService.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/DefaultTinkerResultService.java new file mode 100644 index 00000000..078fb5ad --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/DefaultTinkerResultService.java @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.service; + + +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerLoadResult; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.lib.util.TinkerServiceInternals; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/19. + */ +public class DefaultTinkerResultService extends AbstractResultService { + private static final String TAG = "Tinker.DefaultTinkerResultService"; + + /** + * we may want to use the new patch just now!! + * + * @param result + */ + @Override + public void onPatchResult(PatchResult result) { + if (result == null) { + TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!"); + return; + } + TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString()); + + //first, we want to kill the recover process + TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); + + // if success and newPatch, it is nice to delete the raw file, and restart at once + // only main process can load an upgrade patch! + if (result.isSuccess && result.isUpgradePatch) { + File rawFile = new File(result.rawPatchFilePath); + if (rawFile.exists()) { + TinkerLog.i(TAG, "save delete raw patch file"); + SharePatchFileUtil.safeDeleteFile(rawFile); + } + if (checkIfNeedKill(result)) { + android.os.Process.killProcess(android.os.Process.myPid()); + } else { + TinkerLog.i(TAG, "I have already install the newly patch version!"); + } + } + + //repair current patch fail, just clean! + if (!result.isSuccess && !result.isUpgradePatch) { + Tinker.with(getApplicationContext()).cleanPatch(); + } + } + + public boolean checkIfNeedKill(PatchResult result) { + Tinker tinker = Tinker.with(getApplicationContext()); + if (tinker.isTinkerLoaded()) { + TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent(); + if (tinkerLoadResult != null) { + String currentVersion = tinkerLoadResult.currentVersion; + if (result.patchVersion != null && result.patchVersion.equals(currentVersion)) { + return false; + } + } + } + return true; + } + + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java new file mode 100644 index 00000000..7de1e3d9 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java @@ -0,0 +1,63 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.service; + +import java.io.Serializable; + +/** + * Created by zhangshaowen on 16/3/19. + */ +public class PatchResult implements Serializable { + public boolean isUpgradePatch; + + public boolean isSuccess; + + public String rawPatchFilePath; + + public long costTime; + + public Throwable e; + + //@Nullable + public String patchVersion; + + //@Nullable + public String patchTinkerID; + + //@Nullable + public String baseTinkerID; + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("\nPatchResult: \n"); + sb.append("isUpgradePatch:" + isUpgradePatch + "\n"); + sb.append("isSuccess:" + isSuccess + "\n"); + sb.append("rawPatchFilePath:" + rawPatchFilePath + "\n"); + sb.append("costTime:" + costTime + "\n"); + sb.append("patchVersion:" + patchVersion + "\n"); + sb.append("patchTinkerID:" + patchTinkerID + "\n"); + sb.append("baseTinkerID:" + baseTinkerID + "\n"); + + if (e != null) { + sb.append("Throwable:" + e.getMessage() + "\n"); + } else { + sb.append("Throwable: null" + "\n"); + } + return sb.toString(); + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java new file mode 100644 index 00000000..75f254af --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java @@ -0,0 +1,193 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.service; + +import android.app.IntentService; +import android.app.Notification; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; +import android.os.SystemClock; + +import com.tencent.tinker.lib.patch.AbstractPatch; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/14. + */ +public class TinkerPatchService extends IntentService { + private static final String TAG = "Tinker.TinkerPatchService"; + + private static final String PATCH_PATH_EXTRA = "patch_path_extra"; + private static final String PATCH_NEW_EXTRA = "patch_new_extra"; + private static AbstractPatch upgradePatchProcessor = null; + private static AbstractPatch repairPatchProcessor = null; + private static int notificationId = ShareConstants.TINKER_PATCH_SERVICE_NOTIFICATION; + + /** + * Creates an IntentService. Invoked by your subclass's constructor. + */ + public TinkerPatchService() { + super(TinkerPatchService.class.getSimpleName()); + } + + public static void runPatchService(Context context, String path, boolean isUpgradePatch) { + Intent intent = new Intent(context, TinkerPatchService.class); + intent.putExtra(PATCH_PATH_EXTRA, path); + intent.putExtra(PATCH_NEW_EXTRA, isUpgradePatch); + + context.startService(intent); + } + + public static void setPatchProcessor(AbstractPatch upgradePatch, AbstractPatch repairPatch) { + upgradePatchProcessor = upgradePatch; + repairPatchProcessor = repairPatch; + } + + public static String getPatchPathExtra(Intent intent) { + if (intent == null) { + throw new TinkerRuntimeException("getPatchPathExtra, but intent is null"); + } + return ShareIntentUtil.getStringExtra(intent, PATCH_PATH_EXTRA); + } + + public static boolean getPatchUpgradeExtra(Intent intent) { + if (intent == null) { + throw new TinkerRuntimeException("getPatchUpgradeExtra, but intent is null"); + } + return ShareIntentUtil.getBooleanExtra(intent, PATCH_NEW_EXTRA, false); + } + + /** + * set the tinker notification id you want + * @param id + */ + public static void setTinkerNotificationId(int id) { + notificationId = id; + } + + @Override + protected void onHandleIntent(Intent intent) { + final Context context = getApplicationContext(); + Tinker tinker = Tinker.with(context); + tinker.getPatchReporter().onPatchServiceStart(intent); + + if (intent == null) { + TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring."); + return; + } + String path = getPatchPathExtra(intent); + if (path == null) { + TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring."); + return; + } + File patchFile = new File(path); + + boolean isUpgradePatch = getPatchUpgradeExtra(intent); + + long begin = SystemClock.elapsedRealtime(); + boolean result; + long cost; + Throwable e = null; + + increasingPriority(); + PatchResult patchResult = new PatchResult(); + try { + if (isUpgradePatch) { + if (upgradePatchProcessor == null) { + throw new TinkerRuntimeException("upgradePatchProcessor is null."); + } + result = upgradePatchProcessor.tryPatch(context, path, patchResult); + + } else { + //just recover from exist patch + if (repairPatchProcessor == null) { + throw new TinkerRuntimeException("upgradePatchProcessor is null."); + } + result = repairPatchProcessor.tryPatch(context, path, patchResult); + } + } catch (Throwable throwable) { + e = throwable; + result = false; + tinker.getPatchReporter().onPatchException(patchFile, e, isUpgradePatch); + } + + cost = SystemClock.elapsedRealtime() - begin; + tinker.getPatchReporter(). + onPatchResult(patchFile, result, cost, isUpgradePatch); + + patchResult.isSuccess = result; + patchResult.isUpgradePatch = isUpgradePatch; + patchResult.rawPatchFilePath = path; + patchResult.costTime = cost; + patchResult.e = e; + + AbstractResultService.runResultService(context, patchResult); + + } + + private void increasingPriority() { + TinkerLog.i(TAG, "try to increase patch process priority"); + Notification notification = new Notification(); + if (Build.VERSION.SDK_INT < 18) { + startForeground(notificationId, notification); + } else { + startForeground(notificationId, notification); + // start InnerService + startService(new Intent(this, InnerService.class)); + } + } + + /** + * I don't want to do this, believe me + */ + //InnerService + public static class InnerService extends Service { + @Override + public void onCreate() { + super.onCreate(); + try { + startForeground(notificationId, new Notification()); + } catch (NullPointerException e) { + TinkerLog.e(TAG, "InnerService set service for push exception:%s.", e); + } + // kill + stopSelf(); + } + + @Override + public void onDestroy() { + stopForeground(true); + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + } + +} + diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java new file mode 100644 index 00000000..4d5be29c --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java @@ -0,0 +1,409 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.tinker; + +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.listener.DefaultPatchListener; +import com.tencent.tinker.lib.listener.PatchListener; +import com.tencent.tinker.lib.patch.AbstractPatch; +import com.tencent.tinker.lib.patch.RepairPatch; +import com.tencent.tinker.lib.patch.UpgradePatch; +import com.tencent.tinker.lib.reporter.DefaultLoadReporter; +import com.tencent.tinker.lib.reporter.DefaultPatchReporter; +import com.tencent.tinker.lib.reporter.LoadReporter; +import com.tencent.tinker.lib.reporter.PatchReporter; +import com.tencent.tinker.lib.service.AbstractResultService; +import com.tencent.tinker.lib.service.DefaultTinkerResultService; +import com.tencent.tinker.lib.service.TinkerPatchService; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.lib.util.TinkerServiceInternals; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/10. + */ +public class Tinker { + private static final String TAG = "Tinker.Tinker"; + + private static Tinker sInstance; + private static boolean installed = false; + final Context context; + /** + * data dir, such as /data/data/tinker.sample.android/tinker + */ + final File patchDirectory; + final PatchListener listener; + final LoadReporter loadReporter; + final PatchReporter patchReporter; + final File patchInfoFile; + final boolean isMainProcess; + final boolean isPatchProcess; + /** + * same with {@code TinkerApplication.tinkerLoadVerifyFlag} + */ + final boolean tinkerLoadVerifyFlag; + /** + * same with {@code TinkerApplication.tinkerFlags} + */ + int tinkerFlags; + TinkerLoadResult tinkerLoadResult; + /** + * whether load patch success + */ + private boolean loaded = false; + + private Tinker(Context context, int tinkerFlags, LoadReporter loadReporter, PatchReporter patchReporter, + PatchListener listener, File patchDirectory, File patchInfoFile, + boolean isInMainProc, boolean isPatchProcess, boolean tinkerLoadVerifyFlag) { + this.context = context; + this.listener = listener; + this.loadReporter = loadReporter; + this.patchReporter = patchReporter; + this.tinkerFlags = tinkerFlags; + this.patchDirectory = patchDirectory; + this.patchInfoFile = patchInfoFile; + this.isMainProcess = isInMainProc; + this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; + this.isPatchProcess = isPatchProcess; + } + + /** + * init with default config tinker + * for safer, you must use @{link TinkerInstaller.install} first! + * + * @param context we will use the application context + * @return the Tinker object + */ + public static Tinker with(Context context) { + if (!installed) { + throw new TinkerRuntimeException("you must install tinker before get tinker sInstance"); + } + if (sInstance == null) { + synchronized (Tinker.class) { + if (sInstance == null) { + sInstance = new Builder(context).build(); + } + } + } + return sInstance; + } + + /** + * create custom tinker by {@link Tinker.Builder} + * please do it when very first your app start. + * + * @param tinker + */ + public static void create(Tinker tinker) { + if (sInstance != null) { + throw new TinkerRuntimeException("Tinker instance is already set."); + } + sInstance = tinker; + } + + /** + * you must install tinker first!! + * + * @param intentResult + * @param serviceClass + * @param upgradePatch + * @param repairPatch + */ + public void install(Intent intentResult, Class serviceClass, + AbstractPatch upgradePatch, AbstractPatch repairPatch + ) { + installed = true; + AbstractResultService.setResultServiceClass(serviceClass); + TinkerPatchService.setPatchProcessor(upgradePatch, repairPatch); + + if (!isTinkerEnabled()) { + TinkerLog.e(TAG, "tinker is disabled"); + return; + } + if (intentResult == null) { + throw new TinkerRuntimeException("intentResult must not be null."); + } + tinkerLoadResult = new TinkerLoadResult(); + tinkerLoadResult.parseTinkerResult(getContext(), intentResult); + //after load code set + loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime); + + if (!loaded) { + TinkerLog.w(TAG, "tinker load fail!"); + } + } + + /** + * set tinkerPatchServiceNotificationId + * @param id + */ + public void setPatchServiceNotificationId(int id) { + TinkerPatchService.setTinkerNotificationId(id); + } + + + /** + * Nullable, should check the loaded flag first + */ + public TinkerLoadResult getTinkerLoadResultIfPresent() { + return tinkerLoadResult; + } + + public void install(Intent intentResult) { + install(intentResult, DefaultTinkerResultService.class, new UpgradePatch(), new RepairPatch()); + } + + public Context getContext() { + return context; + } + + public boolean isMainProcess() { + return isMainProcess; + } + + public boolean isPatchProcess() { + return isPatchProcess; + } + + public void setTinkerDisable() { + tinkerFlags = ShareConstants.TINKER_DISABLE; + } + + public LoadReporter getLoadReporter() { + return loadReporter; + } + + public PatchReporter getPatchReporter() { + return patchReporter; + } + + + public boolean isTinkerEnabled() { + return ShareTinkerInternals.isTinkerEnabled(tinkerFlags); + } + + public boolean isTinkerLoaded() { + return loaded; + } + + public void setTinkerLoaded(boolean isLoaded) { + loaded = isLoaded; + } + + public boolean isTinkerInstalled() { + return installed; + } + + public boolean isTinkerLoadVerify() { + return tinkerLoadVerifyFlag; + } + + public boolean isEnabledForDex() { + return ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags); + } + + public boolean isEnabledForNativeLib() { + return ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlags); + } + + public boolean isEnabledForResource() { + return ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlags); + } + + public File getPatchDirectory() { + return patchDirectory; + } + + public File getPatchInfoFile() { + return patchInfoFile; + } + + public PatchListener getPatchListener() { + return listener; + } + + /** + * clean all patch files + */ + public void cleanPatch() { + if (patchDirectory == null) { + return; + } + if (isTinkerLoaded()) { + TinkerLog.e(TAG, "it is not safety to clean patch when tinker is loaded, you should kill all your process after clean!"); + } + SharePatchFileUtil.deleteDir(patchDirectory); + } + + /** + * clean the patch version files, such as tinker/patch-641e634c + * + * @param versionName + */ + public void cleanPatchByVersion(String versionName) { + if (patchDirectory == null || versionName == null) { + return; + } + String path = patchDirectory.getAbsolutePath() + "/" + versionName; + SharePatchFileUtil.deleteDir(path); + } + + /** + * get the rom size of tinker, use kb + * + * @return + */ + public long getTinkerRomSpace() { + if (patchDirectory == null) { + return 0; + } + + return SharePatchFileUtil.getFileOrDirectorySize(patchDirectory) / 1024; + } + + /** + * try delete the temp version files + * + * @param patchFile + */ + public void cleanPatchByVersion(File patchFile) { + if (patchDirectory == null || patchFile == null || !patchFile.exists()) { + return; + } + String versionName = SharePatchFileUtil.getPatchVersionDirectory(SharePatchFileUtil.getMD5(patchFile)); + cleanPatchByVersion(versionName); + } + + + public static class Builder { + private final Context context; + private final boolean mainProcess; + private final boolean patchProcess; + + private int status = -1; + private LoadReporter loadReporter; + private PatchReporter patchReporter; + private PatchListener listener; + private File patchDirectory; + private File patchInfoFile; + private Boolean tinkerLoadVerifyFlag; + + /** + * Start building a new {@link Tinker} instance. + */ + public Builder(Context context) { + if (context == null) { + throw new TinkerRuntimeException("Context must not be null."); + } + this.context = context; + this.mainProcess = TinkerServiceInternals.isInMainProcess(context); + this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context); + this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context); + if (this.patchDirectory == null) { + TinkerLog.e(TAG, "patchDirectory is null!"); + return; + } + this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath()); + TinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory); + } + + public Builder tinkerFlags(int tinkerFlags) { + if (this.status != -1) { + throw new TinkerRuntimeException("tinkerFlag is already set."); + } + this.status = tinkerFlags; + return this; + } + + public Builder tinkerLoadVerifyFlag(Boolean verifyMd5WhenLoad) { + if (verifyMd5WhenLoad == null) { + throw new TinkerRuntimeException("tinkerLoadVerifyFlag must not be null."); + } + if (this.tinkerLoadVerifyFlag != null) { + throw new TinkerRuntimeException("tinkerLoadVerifyFlag is already set."); + } + this.tinkerLoadVerifyFlag = verifyMd5WhenLoad; + return this; + } + + public Builder loadReport(LoadReporter loadReporter) { + if (loadReporter == null) { + throw new TinkerRuntimeException("loadReporter must not be null."); + } + if (this.loadReporter != null) { + throw new TinkerRuntimeException("loadReporter is already set."); + } + this.loadReporter = loadReporter; + return this; + } + + public Builder patchReporter(PatchReporter patchReporter) { + if (patchReporter == null) { + throw new TinkerRuntimeException("patchReporter must not be null."); + } + if (this.patchReporter != null) { + throw new TinkerRuntimeException("patchReporter is already set."); + } + this.patchReporter = patchReporter; + return this; + } + + public Builder listener(PatchListener listener) { + if (listener == null) { + throw new TinkerRuntimeException("listener must not be null."); + } + if (this.listener != null) { + throw new TinkerRuntimeException("listener is already set."); + } + this.listener = listener; + return this; + } + + public Tinker build() { + if (status == -1) { + status = ShareConstants.TINKER_ENABLE_ALL; + } + + if (loadReporter == null) { + loadReporter = new DefaultLoadReporter(context); + } + + if (patchReporter == null) { + patchReporter = new DefaultPatchReporter(context); + } + + if (listener == null) { + listener = new DefaultPatchListener(context); + } + + if (tinkerLoadVerifyFlag == null) { + tinkerLoadVerifyFlag = false; + } + + return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory, + patchInfoFile, mainProcess, patchProcess, tinkerLoadVerifyFlag); + } + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerApplicationHelper.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerApplicationHelper.java new file mode 100644 index 00000000..b3363b47 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerApplicationHelper.java @@ -0,0 +1,342 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.tinker; + +import android.content.Intent; + +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.app.ApplicationLike; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; +import java.util.HashMap; + +/** + * sometimes, you may want to install tinker later, or never install tinker in some process. + * you can use {@code TinkerApplicationHelper} API to get the tinker status! + * Created by zhangshaowen on 16/6/28. + */ +public class TinkerApplicationHelper { + private static final String TAG = "Tinker.TinkerApplicationHelper"; + + /** + * they can use without Tinker is installed! + * same as {@code Tinker.isTinkerEnabled} + * + * @return + */ + public static boolean isTinkerEnableAll(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + int tinkerFlags = applicationLike.getTinkerFlags(); + return ShareTinkerInternals.isTinkerEnabledAll(tinkerFlags); + } + + /** + * same as {@code Tinker.isEnabledForDex} + * + * @param applicationLike + * @return + */ + public static boolean isTinkerEnableForDex(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + int tinkerFlags = applicationLike.getTinkerFlags(); + return ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags); + } + + /** + * same as {@code Tinker.isEnabledForNativeLib} + * + * @param applicationLike + * @return + */ + public static boolean isTinkerEnableForNativeLib(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + int tinkerFlags = applicationLike.getTinkerFlags(); + return ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlags); + } + + /** + * same as {@code Tinker.isTinkerEnabledForResource} + * + * @param applicationLike + * @return + */ + public static boolean isTinkerEnableForResource(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + int tinkerFlags = applicationLike.getTinkerFlags(); + return ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlags); + } + + /** + * same as {@code Tinker.getPatchDirectory} + * + * @param applicationLike + * @return + */ + public static File getTinkerPatchDirectory(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + return SharePatchFileUtil.getPatchDirectory(applicationLike.getApplication()); + } + + /** + * whether tinker is success loaded + * same as {@code Tinker.isTinkerLoaded} + * + * @param applicationLike + * @return + */ + public static boolean isTinkerLoadSuccess(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); + + if (tinkerResultIntent == null) { + return false; + } + int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); + + return (loadCode == ShareConstants.ERROR_LOAD_OK); + } + + /** + * you can use this api to get load dexes before tinker is installed + * same as {@code Tinker.getTinkerLoadResultIfPresent.dexes} + * + * @return + */ + public static HashMap getLoadDexesAndMd5(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); + + if (tinkerResultIntent == null) { + return null; + } + int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); + + if (loadCode == ShareConstants.ERROR_LOAD_OK) { + return ShareIntentUtil.getIntentPatchDexPaths(tinkerResultIntent); + } + return null; + } + + + /** + * you can use this api to get load libs before tinker is installed + * same as {@code Tinker.getTinkerLoadResultIfPresent.libs} + * + * @return + */ + public static HashMap getLoadLibraryAndMd5(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); + + if (tinkerResultIntent == null) { + return null; + } + int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); + + if (loadCode == ShareConstants.ERROR_LOAD_OK) { + return ShareIntentUtil.getIntentPatchLibsPaths(tinkerResultIntent); + } + return null; + } + + /** + * you can use this api to get tinker package configs before tinker is installed + * same as {@code Tinker.getTinkerLoadResultIfPresent.packageConfig} + * + * @return + */ + public static HashMap getPackageConfigs(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); + + if (tinkerResultIntent == null) { + return null; + } + int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); + + if (loadCode == ShareConstants.ERROR_LOAD_OK) { + return ShareIntentUtil.getIntentPackageConfig(tinkerResultIntent); + } + return null; + } + + /** + * you can use this api to get tinker current version before tinker is installed + * + * @return + */ + public static String getCurrentVersion(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + + Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); + + if (tinkerResultIntent == null) { + return null; + } + final String oldVersion = ShareIntentUtil.getStringExtra(tinkerResultIntent, ShareIntentUtil.INTENT_PATCH_OLD_VERSION); + final String newVersion = ShareIntentUtil.getStringExtra(tinkerResultIntent, ShareIntentUtil.INTENT_PATCH_NEW_VERSION); + final boolean isMainProcess = ShareTinkerInternals.isInMainProcess(applicationLike.getApplication()); + if (oldVersion != null && newVersion != null) { + if (isMainProcess) { + return newVersion; + } else { + return oldVersion; + } + } + return null; + } + + /** + * clean all patch files without install tinker + * same as {@code Tinker.cleanPatch} + * + * @param applicationLike + */ + public static void cleanPatch(ApplicationLike applicationLike) { + if (applicationLike == null || applicationLike.getApplication() == null) { + throw new TinkerRuntimeException("tinkerApplication is null"); + } + if (TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { + TinkerLog.e(TAG, "it is not safety to clean patch when tinker is loaded, you should kill all your process after clean!"); + } + SharePatchFileUtil.deleteDir(SharePatchFileUtil.getPatchDirectory(applicationLike.getApplication())); + } + + /** + * only support auto load lib/armeabi-v7a library from patch. + * in some process, you may not want to install tinker + * and you can load patch dex and library without install tinker! + * } + */ + public static void loadArmV7aLibrary(ApplicationLike applicationLike, String libName) { + if (libName == null || libName.isEmpty() || applicationLike == null) { + throw new TinkerRuntimeException("libName or context is null!"); + } + + if (TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { + if (TinkerApplicationHelper.loadLibraryFromTinker(applicationLike, "lib/armeabi-v7a", libName)) { + return; + } + + } + System.loadLibrary(libName); + } + + + /** + * only support auto load lib/armeabi library from patch. + * in some process, you may not want to install tinker + * and you can load patch dex and library without install tinker! + */ + public static void loadArmLibrary(ApplicationLike applicationLike, String libName) { + if (libName == null || libName.isEmpty() || applicationLike == null) { + throw new TinkerRuntimeException("libName or context is null!"); + } + + if (TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { + if (TinkerApplicationHelper.loadLibraryFromTinker(applicationLike, "lib/armeabi", libName)) { + return; + } + + } + System.loadLibrary(libName); + } + + /** + * you can use these api to load tinker library without tinker is installed! + * same as {@code TinkerInstaller#loadLibraryFromTinker} + * + * @param applicationLike + * @param relativePath + * @param libname + * @return + * @throws UnsatisfiedLinkError + */ + public static boolean loadLibraryFromTinker(ApplicationLike applicationLike, String relativePath, String libname) throws UnsatisfiedLinkError { + libname = libname.startsWith("lib") ? libname : "lib" + libname; + libname = libname.endsWith(".so") ? libname : libname + ".so"; + String relativeLibPath = relativePath + "/" + libname; + + //TODO we should add cpu abi, and the real path later + if (TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike) + && TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { + HashMap loadLibraries = TinkerApplicationHelper.getLoadLibraryAndMd5(applicationLike); + if (loadLibraries != null) { + String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike); + if (ShareTinkerInternals.isNullOrNil(currentVersion)) { + return false; + } + File patchDirectory = SharePatchFileUtil.getPatchDirectory(applicationLike.getApplication()); + if (patchDirectory == null) { + return false; + } + File patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + SharePatchFileUtil.getPatchVersionDirectory(currentVersion)); + String libPrePath = patchVersionDirectory.getAbsolutePath() + "/" + ShareConstants.SO_PATH; + + for (String name : loadLibraries.keySet()) { + if (name.equals(relativeLibPath)) { + String patchLibraryPath = libPrePath + "/" + name; + File library = new File(patchLibraryPath); + if (library.exists()) { + //whether we check md5 when load + boolean verifyMd5 = applicationLike.getTinkerLoadVerifyFlag(); + if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadLibraries.get(name))) { + //do not report, because tinker is not install + TinkerLog.i(TAG, "loadLibraryFromTinker md5mismatch fail:" + patchLibraryPath); + } else { + System.load(patchLibraryPath); + TinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath); + return true; + } + } + } + } + } + } + return false; + } +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerInstaller.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerInstaller.java new file mode 100644 index 00000000..f0b52b0a --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerInstaller.java @@ -0,0 +1,205 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.tinker; + +import android.content.Context; + +import com.tencent.tinker.lib.listener.PatchListener; +import com.tencent.tinker.lib.patch.AbstractPatch; +import com.tencent.tinker.lib.reporter.LoadReporter; +import com.tencent.tinker.lib.reporter.PatchReporter; +import com.tencent.tinker.lib.service.AbstractResultService; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.app.ApplicationLike; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/19. + */ +public class TinkerInstaller { + private static final String TAG = "Tinker.TinkerInstaller"; + + /** + * install tinker with default config, you must install tinker before you use their api + * or you can just use {@link TinkerApplicationHelper}'s api + * + * @param applicationLike + */ + public static void install(ApplicationLike applicationLike) { + Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build(); + tinker.install(applicationLike.getTinkerResultIntent()); + } + + /** + * install tinker with custom config, you must install tinker before you use their api + * or you can just use {@link TinkerApplicationHelper}'s api + * + * @param applicationLike + * @param loadReporter + * @param patchReporter + * @param listener + * @param resultServiceClass + * @param upgradePatchProcessor + * @param repairPatchProcessor + */ + public static void install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter, + PatchListener listener, Class resultServiceClass, + AbstractPatch upgradePatchProcessor, AbstractPatch repairPatchProcessor) { + + Tinker tinker = new Tinker.Builder(applicationLike.getApplication()) + .tinkerFlags(applicationLike.getTinkerFlags()) + .loadReport(loadReporter) + .listener(listener) + .patchReporter(patchReporter) + .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build(); + + Tinker.create(tinker); + tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor, repairPatchProcessor); + + + } + + /** + * clean all patch files! + * + * @param context + */ + public static void cleanPatch(Context context) { + Tinker.with(context).cleanPatch(); + } + + /** + * new patch file to install, try install them with :patch process + * + * @param context + * @param patchLocation + */ + public static void onReceiveUpgradePatch(Context context, String patchLocation) { + Tinker.with(context).getPatchListener().onPatchReceived(patchLocation, true); + } + + /** + * some file does not exist, repair them with :patch process + * Generally you will not use it + * + * @param context + * @param patchLocation + */ + public static void onReceiveRepairPatch(Context context, String patchLocation) { + Tinker.with(context).getPatchListener().onPatchReceived(patchLocation, false); + } + + /** + * set logIml for TinkerLog + * + * @param imp + */ + public static void setLogIml(TinkerLog.TinkerLogImp imp) { + TinkerLog.setTinkerLogImp(imp); + } + + /** + * sample usage for native library + * + * @param context + * @param relativePath such as lib/armeabi + * @param libname for the lib libTest.so, you can pass Test or libTest, or libTest.so + * @return boolean + * @throws UnsatisfiedLinkError + */ + public static boolean loadLibraryFromTinker(Context context, String relativePath, String libname) throws UnsatisfiedLinkError { + final Tinker tinker = Tinker.with(context); + + libname = libname.startsWith("lib") ? libname : "lib" + libname; + libname = libname.endsWith(".so") ? libname : libname + ".so"; + String relativeLibPath = relativePath + "/" + libname; + + //TODO we should add cpu abi, and the real path later + if (tinker.isEnabledForNativeLib() && tinker.isTinkerLoaded()) { + TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent(); + if (loadResult.libs != null) { + for (String name : loadResult.libs.keySet()) { + if (name.equals(relativeLibPath)) { + String patchLibraryPath = loadResult.libraryDirectory + "/" + name; + File library = new File(patchLibraryPath); + if (library.exists()) { + //whether we check md5 when load + boolean verifyMd5 = tinker.isTinkerLoadVerify(); + if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadResult.libs.get(name))) { + tinker.getLoadReporter().onLoadFileMd5Mismatch(library, ShareConstants.TYPE_LIBRARY); + } else { + System.load(patchLibraryPath); + TinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath); + return true; + } + } + } + } + } + } + + return false; + } + + /** + * you can use TinkerInstaller.loadLibrary replace your System.loadLibrary for auto update library! + * only support auto load lib/armeabi library from patch. + * for other library in lib/* or assets, + * you can load through {@code TinkerInstaller#loadLibraryFromTinker} + */ + public static void loadArmLibrary(Context context, String libName) { + if (libName == null || libName.isEmpty() || context == null) { + throw new TinkerRuntimeException("libName or context is null!"); + } + + Tinker tinker = Tinker.with(context); + if (tinker.isEnabledForNativeLib()) { + if (TinkerInstaller.loadLibraryFromTinker(context, "lib/armeabi", libName)) { + return; + } + + } + System.loadLibrary(libName); + } + + /** + * you can use TinkerInstaller.loadArmV7Library replace your System.loadLibrary for auto update library! + * only support auto load lib/armeabi-v7a library from patch. + * for other library in lib/* or assets, + * you can load through {@code TinkerInstaller#loadLibraryFromTinker} + */ + public static void loadArmV7Library(Context context, String libName) { + if (libName == null || libName.isEmpty() || context == null) { + throw new TinkerRuntimeException("libName or context is null!"); + } + + Tinker tinker = Tinker.with(context); + if (tinker.isEnabledForNativeLib()) { + if (TinkerInstaller.loadLibraryFromTinker(context, "lib/armeabi-v7a", libName)) { + return; + } + + } + System.loadLibrary(libName); + } + + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerLoadResult.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerLoadResult.java new file mode 100644 index 00000000..46316274 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerLoadResult.java @@ -0,0 +1,362 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.tinker; + +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; +import java.util.HashMap; + +/** + * Created by zhangshaowen on 16/3/25. + */ +public class TinkerLoadResult { + private static final String TAG = "Tinker.TinkerLoadResult"; + //@Nullable + public SharePatchInfo patchInfo; + //@Nullable + public String currentVersion; + + public boolean versionChanged; + //@Nullable + public File patchVersionDirectory; + //@Nullable + public File patchVersionFile; + //@Nullable + public File dexDirectory; + //@Nullable + public File libraryDirectory; + //@Nullable + public File resourceDirectory; + //@Nullable + public File resourceFile; + //@Nullable + public HashMap dexes; + //@Nullable + public HashMap libs; + //@Nullable + public HashMap packageConfig; + + public int loadCode; + + public long costTime; + + public boolean parseTinkerResult(Context context, Intent intentResult) { + Tinker tinker = Tinker.with(context); + loadCode = ShareIntentUtil.getIntentReturnCode(intentResult); + TinkerLog.i(TAG, "parseTinkerResult loadCode:%d", loadCode); + + costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult); + //@Nullable + final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION); + //@Nullable + final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION); + + final File patchDirectory = tinker.getPatchDirectory(); + final File patchInfoFile = tinker.getPatchInfoFile(); + + final boolean isMainProcess = tinker.isMainProcess(); + + + if (oldVersion != null && newVersion != null) { + if (isMainProcess) { + currentVersion = newVersion; + } else { + currentVersion = oldVersion; + } + + TinkerLog.i(TAG, "parseTinkerResult oldVersion:%s, newVersion:%s, current:%s", oldVersion, newVersion, + currentVersion); + //current version may be nil + String patchName = SharePatchFileUtil.getPatchVersionDirectory(currentVersion); + if (!ShareTinkerInternals.isNullOrNil(patchName)) { + patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + patchName); + patchVersionFile = new File(patchVersionDirectory.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(currentVersion)); + dexDirectory = new File(patchVersionDirectory, ShareConstants.DEX_PATH); + libraryDirectory = new File(patchVersionDirectory, ShareConstants.SO_PATH); + resourceDirectory = new File(patchVersionDirectory, ShareConstants.RES_PATH); + resourceFile = new File(resourceDirectory, ShareConstants.RES_NAME); + } + patchInfo = new SharePatchInfo(oldVersion, newVersion); + versionChanged = !(oldVersion.equals(newVersion)); + } + + //found uncaught exception, just return + Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult); + if (exception != null) { + TinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode); + int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; + switch (loadCode) { + case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION: + errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION: + errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX; + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION: + errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE; + break; + case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION: + errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT; + break; + } + tinker.getLoadReporter().onLoadException(exception, errorCode); + return false; + } + + switch (loadCode) { + case ShareConstants.ERROR_LOAD_GET_INTENT_FAIL: + TinkerLog.e(TAG, "can't get the right intent return code"); + throw new TinkerRuntimeException("can't get the right intent return code"); +// break; + case ShareConstants.ERROR_LOAD_DISABLE: + TinkerLog.w(TAG, "tinker is disable, just return"); + break; +// case ShareConstants.ERROR_LOAD_PATCH_NOT_SUPPORTED: +// TinkerLog.w(TAG, "tinker is not supported, just return"); +// break; + case ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST: + case ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST: + TinkerLog.w(TAG, "can't find patch file, is ok, just return"); + break; + + case ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED: + TinkerLog.e(TAG, "path info corrupted"); + tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); + break; + + case ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK: + TinkerLog.e(TAG, "path info blank, wait main process to restart"); + break; + + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST: + TinkerLog.e(TAG, "patch version directory not found, current version:%s", currentVersion); + tinker.getLoadReporter().onLoadFileNotFound(patchVersionDirectory, + ShareConstants.TYPE_PATCH_FILE, true); + break; + + case ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST: + TinkerLog.e(TAG, "patch version file not found, current version:%s", currentVersion); + if (patchVersionFile == null) { + throw new TinkerRuntimeException("error load patch version file not exist, but file is null"); + } + tinker.getLoadReporter().onLoadFileNotFound(patchVersionFile, + ShareConstants.TYPE_PATCH_FILE, false); + break; + case ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL: + TinkerLog.i(TAG, "patch package check fail"); + if (patchVersionFile == null) { + throw new TinkerRuntimeException("error patch package check fail , but file is null"); + } + int errorCode = intentResult.getIntExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); + tinker.getLoadReporter().onLoadPackageCheckFail(patchVersionFile, errorCode); + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST: + if (dexDirectory != null) { + TinkerLog.e(TAG, "patch dex file directory not found:%s", dexDirectory.getAbsolutePath()); + tinker.getLoadReporter().onLoadFileNotFound(dexDirectory, + ShareConstants.TYPE_DEX, true); + } else { + //should be not here + TinkerLog.e(TAG, "patch dex file directory not found, warning why the path is null!!!!"); + throw new TinkerRuntimeException("patch dex file directory not found, warning why the path is null!!!!"); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST: + String dexPath = ShareIntentUtil.getStringExtra(intentResult, + ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); + if (dexPath != null) { + //we only pass one missing file + TinkerLog.e(TAG, "patch dex file not found:%s", dexPath); + tinker.getLoadReporter().onLoadFileNotFound(new File(dexPath), + ShareConstants.TYPE_DEX, false); + + } else { + TinkerLog.e(TAG, "patch dex file not found, but path is null!!!!"); + throw new TinkerRuntimeException("patch dex file not found, but path is null!!!!"); +// tinker.getLoadReporter().onLoadFileNotFound(null, +// ShareConstants.TYPE_DEX, false); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST: + String dexOptPath = ShareIntentUtil.getStringExtra(intentResult, + ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); + if (dexOptPath != null) { + //we only pass one missing file + TinkerLog.e(TAG, "patch dex opt file not found:%s", dexOptPath); + tinker.getLoadReporter().onLoadFileNotFound(new File(dexOptPath), + ShareConstants.TYPE_DEX_OPT, false); + + } else { + TinkerLog.e(TAG, "patch dex opt file not found, but path is null!!!!"); + throw new TinkerRuntimeException("patch dex opt file not found, but path is null!!!!"); +// tinker.getLoadReporter().onLoadFileNotFound(null, +// ShareConstants.TYPE_DEX, false); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST: + if (patchVersionDirectory != null) { + TinkerLog.e(TAG, "patch lib file directory not found:%s", libraryDirectory.getAbsolutePath()); + tinker.getLoadReporter().onLoadFileNotFound(libraryDirectory, + ShareConstants.TYPE_LIBRARY, true); + } else { + //should be not here + TinkerLog.e(TAG, "patch lib file directory not found, warning why the path is null!!!!"); + throw new TinkerRuntimeException("patch lib file directory not found, warning why the path is null!!!!"); + +// tinker.getLoadReporter().onLoadFileNotFound(null, +// ShareConstants.TYPE_LIBRARY, true); + } + + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST: + String libPath = ShareIntentUtil.getStringExtra(intentResult, + ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH); + if (libPath != null) { + //we only pass one missing file and then we break + TinkerLog.e(TAG, "patch lib file not found:%s", libPath); + tinker.getLoadReporter().onLoadFileNotFound(new File(libPath), + ShareConstants.TYPE_LIBRARY, false); + } else { + TinkerLog.e(TAG, "patch lib file not found, but path is null!!!!"); + throw new TinkerRuntimeException("patch lib file not found, but path is null!!!!"); +// tinker.getLoadReporter().onLoadFileNotFound(null, +// ShareConstants.TYPE_LIBRARY, false); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL: + TinkerLog.e(TAG, "patch dex load fail, classloader is null"); + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH: + String mismatchPath = ShareIntentUtil.getStringExtra(intentResult, + ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH); + if (mismatchPath == null) { + TinkerLog.e(TAG, "patch dex file md5 is mismatch, but path is null!!!!"); + throw new TinkerRuntimeException("patch dex file md5 is mismatch, but path is null!!!!"); + } else { + TinkerLog.e(TAG, "patch dex file md5 is mismatch: %s", mismatchPath); + tinker.getLoadReporter().onLoadFileMd5Mismatch(new File(mismatchPath), + ShareConstants.TYPE_DEX); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL: + TinkerLog.i(TAG, "rewrite patch info file corrupted"); + tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST: + if (patchVersionDirectory != null) { + TinkerLog.e(TAG, "patch resource file directory not found:%s", resourceDirectory.getAbsolutePath()); + tinker.getLoadReporter().onLoadFileNotFound(resourceDirectory, + ShareConstants.TYPE_RESOURCE, true); + } else { + //should be not here + TinkerLog.e(TAG, "patch resource file directory not found, warning why the path is null!!!!"); + throw new TinkerRuntimeException("patch resource file directory not found, warning why the path is null!!!!"); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST: + if (patchVersionDirectory != null) { + TinkerLog.e(TAG, "patch resource file not found:%s", resourceFile.getAbsolutePath()); + tinker.getLoadReporter().onLoadFileNotFound(resourceFile, + ShareConstants.TYPE_RESOURCE, false); + } else { + //should be not here + TinkerLog.e(TAG, "patch resource file not found, warning why the path is null!!!!"); + throw new TinkerRuntimeException("patch resource file not found, warning why the path is null!!!!"); + } + break; + case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH: + if (resourceFile == null) { + TinkerLog.e(TAG, "resource file md5 mismatch, but patch resource file not found!"); + throw new TinkerRuntimeException("resource file md5 mismatch, but patch resource file not found!"); + } + TinkerLog.e(TAG, "patch resource file md5 is mismatch: %s", resourceFile.getAbsolutePath()); + + tinker.getLoadReporter().onLoadFileMd5Mismatch(resourceFile, + ShareConstants.TYPE_RESOURCE); + break; + case ShareConstants.ERROR_LOAD_OK: + TinkerLog.i(TAG, "oh yeah, tinker load all success"); + tinker.setTinkerLoaded(true); + //get load dex + dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult); + libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult); + + packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult); + + if (isMainProcess && versionChanged) { + //change the old version to new + patchInfo.oldVersion = currentVersion; + tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName()); + + } + return true; + } + return false; + + } + + /** + * get the base tinkerId + * + * @return + */ + public String getTinkerID() { + if (packageConfig != null) { + String tinkerId = packageConfig.get(ShareConstants.TINKER_ID); + return tinkerId; + } + return null; + } + + /** + * get the new tinkerId + * + * @return + */ + public String getNewTinkerID() { + if (packageConfig != null) { + String tinkerId = packageConfig.get(ShareConstants.NEW_TINKER_ID); + + return tinkerId; + } + return null; + } + + /** + * get package configs + * + * @param name + * @return + */ + public String getPackageConfigByName(String name) { + if (packageConfig != null) { + return packageConfig.get(name); + } + return null; + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerLog.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerLog.java new file mode 100644 index 00000000..cb677c5d --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerLog.java @@ -0,0 +1,131 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.util; + +/** + * Created by zhangshaowen on 16/3/17. + */ + + +public class TinkerLog { + private static final String TAG = "Tinker.TinkerLog"; + private static TinkerLogImp debugLog = new TinkerLogImp() { + + @Override + public void v(final String tag, final String msg, final Object... obj) { + String log = obj == null ? msg : String.format(msg, obj); + android.util.Log.v(tag, log); + } + + @Override + public void i(final String tag, final String msg, final Object... obj) { + String log = obj == null ? msg : String.format(msg, obj); + android.util.Log.i(tag, log); + + } + + @Override + public void d(final String tag, final String msg, final Object... obj) { + String log = obj == null ? msg : String.format(msg, obj); + android.util.Log.d(tag, log); + } + + @Override + public void w(final String tag, final String msg, final Object... obj) { + String log = obj == null ? msg : String.format(msg, obj); + android.util.Log.w(tag, log); + } + + @Override + public void e(final String tag, final String msg, final Object... obj) { + String log = obj == null ? msg : String.format(msg, obj); + android.util.Log.e(tag, log); + } + + @Override + public void printErrStackTrace(String tag, Throwable tr, String format, Object... obj) { + String log = obj == null ? format : String.format(format, obj); + if (log == null) { + log = ""; + } + log += " " + android.util.Log.getStackTraceString(tr); + android.util.Log.e(tag, log); + } + }; + private static TinkerLogImp tinkerLogImp = debugLog; + + public static void setTinkerLogImp(TinkerLogImp imp) { + tinkerLogImp = imp; + } + + public static TinkerLogImp getImpl() { + return tinkerLogImp; + } + + public static void v(final String tag, final String msg, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.v(tag, msg, obj); + } + } + + public static void e(final String tag, final String msg, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.e(tag, msg, obj); + } + } + + public static void w(final String tag, final String msg, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.w(tag, msg, obj); + } + } + + public static void i(final String tag, final String msg, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.i(tag, msg, obj); + } + } + + public static void d(final String tag, final String msg, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.d(tag, msg, obj); + } + } + + public static void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj) { + if (tinkerLogImp != null) { + tinkerLogImp.printErrStackTrace(tag, tr, format, obj); + } + } + + public interface TinkerLogImp { + + void v(final String tag, final String msg, final Object... obj); + + void i(final String tag, final String msg, final Object... obj); + + void w(final String tag, final String msg, final Object... obj); + + void d(final String tag, final String msg, final Object... obj); + + void e(final String tag, final String msg, final Object... obj); + + void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj); + + } + +} diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerServiceInternals.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerServiceInternals.java new file mode 100644 index 00000000..2118e23b --- /dev/null +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerServiceInternals.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.lib.util; + +import android.app.ActivityManager; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.util.Log; + +import com.tencent.tinker.lib.service.TinkerPatchService; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.util.List; + +/** + * Created by zhangshaowen on 16/3/10. + */ +public class TinkerServiceInternals extends ShareTinkerInternals { + private static final String TAG = "Tinker.ServiceInternals"; + + /** + * or you may just hardcode them in your app + */ + private static String patchServiceProcessName = null; + + public static void killTinkerPatchServiceProcess(Context context) { + String serverProcessName = getTinkerPatchServiceName(context); + if (serverProcessName == null) { + return; + } + + final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + for (ActivityManager.RunningAppProcessInfo appProcess : am.getRunningAppProcesses()) { + String processName = appProcess.processName; + if (processName.equals(serverProcessName)) { + android.os.Process.killProcess(appProcess.pid); + } + } + + } + + public static boolean isTinkerPatchServiceRunning(Context context) { + final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + String serverName = getTinkerPatchServiceName(context); + if (serverName == null) { + return false; + } + try { + // ActivityManagergetRunningAppProcesses() + List appProcessList = am + .getRunningAppProcesses(); + + for (ActivityManager.RunningAppProcessInfo appProcess : appProcessList) { + String processName = appProcess.processName; + if (processName.equals(serverName)) { + return true; + } + } + } catch (Exception e) { + Log.e(TAG, "isTinkerPatchServiceRunning Exception: " + e.toString()); + return false; + } catch (Error e) { + Log.e(TAG, "isTinkerPatchServiceRunning Error: " + e.toString()); + return false; + } + + return false; + } + + + public static String getTinkerPatchServiceName(final Context context) { + if (patchServiceProcessName != null) { + return patchServiceProcessName; + } + //may be null, and you may like to hardcode instead + String serviceName = TinkerServiceInternals.getServiceProcessName(context, TinkerPatchService.class); + if (serviceName == null) { + return null; + } + patchServiceProcessName = serviceName; + return patchServiceProcessName; + } + + /** + * add service cache + * + * @param context + * @return boolean + */ + public static boolean isInTinkerPatchServiceProcess(Context context) { + String process = getProcessName(context); + + String service = TinkerServiceInternals.getTinkerPatchServiceName(context); + if (service == null || service.length() == 0) { + return false; + } + return process.equals(service); + } + + private static String getServiceProcessName(Context context, Class serviceClass) { + PackageManager packageManager = context.getPackageManager(); + + ComponentName component = new ComponentName(context, serviceClass); + ServiceInfo serviceInfo; + try { + serviceInfo = packageManager.getServiceInfo(component, 0); + } catch (PackageManager.NameNotFoundException ignored) { + // Service is disabled. + return null; + } + + return serviceInfo.processName; + } + +} diff --git a/tinker-android/tinker-android-lib/src/test/java/com/tencent/tinker/recover/ExampleUnitTest.java b/tinker-android/tinker-android-lib/src/test/java/com/tencent/tinker/recover/ExampleUnitTest.java new file mode 100644 index 00000000..f82a6496 --- /dev/null +++ b/tinker-android/tinker-android-lib/src/test/java/com/tencent/tinker/recover/ExampleUnitTest.java @@ -0,0 +1,31 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.recover; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/.gitignore b/tinker-android/tinker-android-loader/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-android/tinker-android-loader/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-android/tinker-android-loader/build.gradle b/tinker-android/tinker-android-loader/build.gradle new file mode 100644 index 00000000..cb3264ad --- /dev/null +++ b/tinker-android/tinker-android-loader/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' +} + +task buildSdk(type: Copy, dependsOn: [build]) { + from("$buildDir/outputs/aar/") { + include "${project.getName()}-release.aar" + } + + into(rootProject.file("buildSdk/android/")) + rename { String fileName -> + fileName.replace("release", "${version}") + } +} + +apply from: rootProject.file('gradle/android-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/gradle.properties b/tinker-android/tinker-android-loader/gradle.properties new file mode 100644 index 00000000..827e1579 --- /dev/null +++ b/tinker-android/tinker-android-loader/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=tinker-android-loader +POM_NAME=Tinker Android Loader +POM_PACKAGING=jar \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/proguard-rules.pro b/tinker-android/tinker-android-loader/proguard-rules.pro new file mode 100644 index 00000000..ca7ea794 --- /dev/null +++ b/tinker-android/tinker-android-loader/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/tinker-android/tinker-android-loader/src/androidTest/java/com/tencent/tinker/loader/ApplicationTest.java b/tinker-android/tinker-android-loader/src/androidTest/java/com/tencent/tinker/loader/ApplicationTest.java new file mode 100644 index 00000000..1c8f5628 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/androidTest/java/com/tencent/tinker/loader/ApplicationTest.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/src/main/AndroidManifest.xml b/tinker-android/tinker-android-loader/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8b6db826 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AbstractTinkerLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AbstractTinkerLoader.java new file mode 100644 index 00000000..111e38c2 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AbstractTinkerLoader.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.content.Intent; + +import com.tencent.tinker.loader.app.TinkerApplication; + + +/** + * Created by zhangshaowen on 16/4/30. + */ +public abstract class AbstractTinkerLoader { + abstract public Intent tryLoad(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag); +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AndroidNClassLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AndroidNClassLoader.java new file mode 100644 index 00000000..683ba2c0 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AndroidNClassLoader.java @@ -0,0 +1,103 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Context; +import android.os.Build; + +import java.lang.reflect.Field; + +import dalvik.system.PathClassLoader; + +/** + * Created by zhangshaowen on 16/7/24. + */ +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +class AndroidNClassLoader extends PathClassLoader { + PathClassLoader originClassLoader; + + private AndroidNClassLoader(String dexPath, PathClassLoader parent) { + super(dexPath, parent.getParent()); + originClassLoader = parent; + } + + private static AndroidNClassLoader createAndroidNClassLoader(PathClassLoader original) throws Exception { + //let all element "" + AndroidNClassLoader androidNClassLoader = new AndroidNClassLoader("", original); + Object originPathList = findField(original, "pathList").get(original); + Field pathListField = findField(androidNClassLoader, "pathList"); + //just use PathClassloader's pathlist + pathListField.set(androidNClassLoader, originPathList); + return androidNClassLoader; + } + + private static Field findField(Object instance, String name) throws NoSuchFieldException { + for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + + if (!field.isAccessible()) { + field.setAccessible(true); + } + + return field; + } catch (NoSuchFieldException e) { + // ignore and search next + } + } + throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); + } + + private static void reflectPackageInfoClassloader(Application application, ClassLoader reflectClassLoader) throws Exception { + String defBase = "mBase"; + String defPackageInfo = "mPackageInfo"; + String defClassLoader = "mClassLoader"; + + Context baseContext = (Context) findField(application, defBase).get(application); + Object basePackageInfo = findField(baseContext, defPackageInfo).get(baseContext); + Field classLoaderField = findField(basePackageInfo, defClassLoader); + Thread.currentThread().setContextClassLoader(reflectClassLoader); + classLoaderField.set(basePackageInfo, reflectClassLoader); + } + + public static AndroidNClassLoader inject(PathClassLoader originClassLoader, Application application) throws Exception { + AndroidNClassLoader classLoader = createAndroidNClassLoader(originClassLoader); + reflectPackageInfoClassloader(application, classLoader); + return classLoader; + } + +// public static String getLdLibraryPath(ClassLoader loader) throws Exception { +// String nativeLibraryPath; +// +// nativeLibraryPath = (String) loader.getClass() +// .getMethod("getLdLibraryPath", new Class[0]) +// .invoke(loader, new Object[0]); +// +// return nativeLibraryPath; +// } + + public Class findClass(String name) throws ClassNotFoundException { + return super.findClass(name); + } + + @Override + public String findLibrary(String name) { + return super.findLibrary(name); + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/SystemClassLoaderAdder.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/SystemClassLoaderAdder.java new file mode 100644 index 00000000..88b67135 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/SystemClassLoaderAdder.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2016 THL A29 Limited, a Tencent company. + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.os.Build; +import android.util.Log; + +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareReflectUtil; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.zip.ZipFile; + +import dalvik.system.DexFile; +import dalvik.system.PathClassLoader; + +/** + * Created by zhangshaowen on 16/3/18. + */ +public class SystemClassLoaderAdder { + private static final String TAG = "Tinker.ClassLoaderAdder"; + + private static final String CHECK_DEX_CLASS = "com.tencent.tinker.loader.TinkerTestDexLoad"; + private static final String CHECK_DEX_FIELD = "isPatch"; + + + @SuppressLint("NewApi") + public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List files) + throws Throwable { + + if (!files.isEmpty()) { + ClassLoader classLoader = loader; + if (Build.VERSION.SDK_INT >= 24) { + classLoader = AndroidNClassLoader.inject(loader, application); + } + //because in dalvik, if inner class is not the same classloader with it wrapper class. + //it won't fail at dex2opt + if (Build.VERSION.SDK_INT >= 23) { + V23.install(classLoader, files, dexOptDir); + } else if (Build.VERSION.SDK_INT >= 19) { + V19.install(classLoader, files, dexOptDir); + } else if (Build.VERSION.SDK_INT >= 14) { + V14.install(classLoader, files, dexOptDir); + } else { + V4.install(classLoader, files, dexOptDir); + } + + if (!checkDexInstall()) { + throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL); + } + } + } + + private static boolean checkDexInstall() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + Class clazz = Class.forName(CHECK_DEX_CLASS); + Field filed = ShareReflectUtil.findField(clazz, CHECK_DEX_FIELD); + boolean isPatch = (boolean) filed.get(null); + Log.w(TAG, "checkDexInstall result:" + isPatch); + return isPatch; + } + + /** + * Installer for platform versions 19. + */ + private static final class V23 { + + private static void install(ClassLoader loader, List additionalClassPathEntries, + File optimizedDirectory) + throws IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { + /* The patched class loader is expected to be a descendant of + * dalvik.system.BaseDexClassLoader. We modify its + * dalvik.system.DexPathList pathList field to append additional DEX + * file entries. + */ + Field pathListField = ShareReflectUtil.findField(loader, "pathList"); + Object dexPathList = pathListField.get(loader); + ArrayList suppressedExceptions = new ArrayList(); + ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, + new ArrayList(additionalClassPathEntries), optimizedDirectory, + suppressedExceptions)); + if (suppressedExceptions.size() > 0) { + for (IOException e : suppressedExceptions) { + Log.w(TAG, "Exception in makePathElement", e); + throw e; + } + + } + } + + /** + * A wrapper around + * {@code private static final dalvik.system.DexPathList#makePathElements}. + */ + private static Object[] makePathElements( + Object dexPathList, ArrayList files, File optimizedDirectory, + ArrayList suppressedExceptions) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + + Method makePathElements; + try { + makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class, + List.class); + } catch (NoSuchMethodException e) { + Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure"); + try { + makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class); + } catch (NoSuchMethodException e1) { + Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure"); + try { + Log.e(TAG, "NoSuchMethodException: try use v19 instead"); + return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions); + } catch (NoSuchMethodException e2) { + Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure"); + throw e2; + } + } + } + + return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); + } + } + + /** + * Installer for platform versions 19. + */ + private static final class V19 { + + private static void install(ClassLoader loader, List additionalClassPathEntries, + File optimizedDirectory) + throws IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { + /* The patched class loader is expected to be a descendant of + * dalvik.system.BaseDexClassLoader. We modify its + * dalvik.system.DexPathList pathList field to append additional DEX + * file entries. + */ + Field pathListField = ShareReflectUtil.findField(loader, "pathList"); + Object dexPathList = pathListField.get(loader); + ArrayList suppressedExceptions = new ArrayList(); + ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, + new ArrayList(additionalClassPathEntries), optimizedDirectory, + suppressedExceptions)); + if (suppressedExceptions.size() > 0) { + for (IOException e : suppressedExceptions) { + Log.w(TAG, "Exception in makeDexElement", e); + throw e; + } + } + } + + /** + * A wrapper around + * {@code private static final dalvik.system.DexPathList#makeDexElements}. + */ + private static Object[] makeDexElements( + Object dexPathList, ArrayList files, File optimizedDirectory, + ArrayList suppressedExceptions) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + + Method makeDexElements = null; + try { + makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, + ArrayList.class); + } catch (NoSuchMethodException e) { + Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure"); + try { + makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", List.class, File.class, List.class); + } catch (NoSuchMethodException e1) { + Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure"); + throw e1; + } + } + + return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); + } + } + + /** + * Installer for platform versions 14, 15, 16, 17 and 18. + */ + private static final class V14 { + + private static void install(ClassLoader loader, List additionalClassPathEntries, + File optimizedDirectory) + throws IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, InvocationTargetException, NoSuchMethodException { + /* The patched class loader is expected to be a descendant of + * dalvik.system.BaseDexClassLoader. We modify its + * dalvik.system.DexPathList pathList field to append additional DEX + * file entries. + */ + Field pathListField = ShareReflectUtil.findField(loader, "pathList"); + Object dexPathList = pathListField.get(loader); + ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, + new ArrayList(additionalClassPathEntries), optimizedDirectory)); + } + + /** + * A wrapper around + * {@code private static final dalvik.system.DexPathList#makeDexElements}. + */ + private static Object[] makeDexElements( + Object dexPathList, ArrayList files, File optimizedDirectory) + throws IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + Method makeDexElements = + ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); + + return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); + } + } + + /** + * Installer for platform versions 4 to 13. + */ + private static final class V4 { + private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) + throws IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, IOException { + /* The patched class loader is expected to be a descendant of + * dalvik.system.DexClassLoader. We modify its + * fields mPaths, mFiles, mZips and mDexs to append additional DEX + * file entries. + */ + int extraSize = additionalClassPathEntries.size(); + + Field pathField = ShareReflectUtil.findField(loader, "path"); + + StringBuilder path = new StringBuilder((String) pathField.get(loader)); + String[] extraPaths = new String[extraSize]; + File[] extraFiles = new File[extraSize]; + ZipFile[] extraZips = new ZipFile[extraSize]; + DexFile[] extraDexs = new DexFile[extraSize]; + for (ListIterator iterator = additionalClassPathEntries.listIterator(); + iterator.hasNext();) { + File additionalEntry = iterator.next(); + String entryPath = additionalEntry.getAbsolutePath(); + path.append(':').append(entryPath); + int index = iterator.previousIndex(); + extraPaths[index] = entryPath; + extraFiles[index] = additionalEntry; + extraZips[index] = new ZipFile(additionalEntry); + //edit by zhangshaowen + String outputPathName = SharePatchFileUtil.optimizedPathFor(additionalEntry, optimizedDirectory); + //for below 4.0, we must input jar or zip + extraDexs[index] = DexFile.loadDex(entryPath, outputPathName, 0); + } + + pathField.set(loader, path.toString()); + ShareReflectUtil.expandFieldArray(loader, "mPaths", extraPaths); + ShareReflectUtil.expandFieldArray(loader, "mFiles", extraFiles); + ShareReflectUtil.expandFieldArray(loader, "mZips", extraZips); + try { + ShareReflectUtil.expandFieldArray(loader, "mDexs", extraDexs); + } catch (Exception e) { + + } + } + } + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java new file mode 100644 index 00000000..b700a1b8 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java @@ -0,0 +1,202 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Intent; +import android.os.Build; +import android.util.Log; + +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareDexDiffPatchInfo; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import dalvik.system.PathClassLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by zhangshaowen on 16/3/8. + * check the complete of the dex files + * pre-load patch dex files + */ +public class TinkerDexLoader { + + private static final String TAG = "Tinker.TinkerDexLoader"; + + private static final String DEX_MEAT_FILE = ShareConstants.DEX_META_FILE; + private static final String DEX_PATH = ShareConstants.DEX_PATH; + private static final String DEX_OPTIMIZE_PATH = ShareConstants.DEX_OPTIMIZE_PATH; + private static final ArrayList dexList = new ArrayList<>(); + + private TinkerDexLoader() { + } + + /** + * Load tinker JARs and add them to + * the Application ClassLoader. + * + * @param application The application. + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public static boolean loadTinkerJars(Application application, boolean tinkerLoadVerifyFlag, String directory, Intent intentResult) { + if (dexList.isEmpty()) { + Log.w(TAG, "there is no dex to load"); + return true; + } + + PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader(); + if (classLoader != null) { + Log.i(TAG, "classloader: " + classLoader.toString()); + } else { + Log.e(TAG, "classloader is null"); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL); + return false; + } + String dexPath = directory + "/" + DEX_PATH + "/"; + File optimizeDir = new File(directory + "/" + DEX_OPTIMIZE_PATH); +// Log.i(TAG, "loadTinkerJars: dex path: " + dexPath); +// Log.i(TAG, "loadTinkerJars: opt path: " + optimizeDir.getAbsolutePath()); + + ArrayList legalFiles = new ArrayList<>(); + + final boolean isArtPlatForm = ShareTinkerInternals.isVmArt(); + for (ShareDexDiffPatchInfo info : dexList) { + //for dalvik, ignore art support dex + if (isJustArtSupportDex(info)) { + continue; + } + String path = dexPath + info.realName; + File file = new File(path); + + if (tinkerLoadVerifyFlag) { + long start = System.currentTimeMillis(); + String checkMd5 = isArtPlatForm ? info.destMd5InArt : info.destMd5InDvm; + if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) { + //it is good to delete the mismatch file + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH); + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH, + file.getAbsolutePath()); + return false; + } + Log.i(TAG, "verify dex file:" + file.getPath() + ", md5 use time: " + (System.currentTimeMillis() - start)); + } + legalFiles.add(file); + } + try { + SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles); + } catch (Throwable e) { + Log.e(TAG, "install dexes failed"); +// e.printStackTrace(); + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION); + return false; + } + Log.i(TAG, "after loaded classloader: " + application.getClassLoader().toString()); + + return true; + } + + /** + * all the dex files in meta file exist? + * fast check, only check whether exist + * + * @param directory + * @return boolean + */ + public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, Intent intentResult) { + String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE); + //not found dex + if (meta == null) { + return true; + } + dexList.clear(); + ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, dexList); + + if (dexList.isEmpty()) { + return true; + } + + HashMap dexes = new HashMap<>(); + + for (ShareDexDiffPatchInfo info : dexList) { + //for dalvik, ignore art support dex + if (isJustArtSupportDex(info)) { + continue; + } + if (!ShareDexDiffPatchInfo.checkDexDiffPatchInfo(info)) { + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); + return false; + } + dexes.put(info.realName, info.destMd5InDvm); + } + //tinker/patch.info/patch-641e634c/dex + String dexDirectory = directory + "/" + DEX_PATH + "/"; + + File dexDir = new File(dexDirectory); + + if (!dexDir.exists() || !dexDir.isDirectory()) { + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST); + return false; + } + + String optimizeDexDirectory = directory + "/" + DEX_OPTIMIZE_PATH + "/"; + File optimizeDexDirectoryFile = new File(optimizeDexDirectory); + + //fast check whether there is any dex files missing + for (String name : dexes.keySet()) { + File dexFile = new File(dexDirectory + name); + if (!dexFile.exists()) { + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexFile.getAbsolutePath()); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST); + return false; + } + //check dex opt whether complete also + File dexOptFile = new File(SharePatchFileUtil.optimizedPathFor(dexFile, optimizeDexDirectoryFile)); + if (!dexOptFile.exists()) { + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexOptFile.getAbsolutePath()); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST); + return false; + } + } + + //if is ok, add to result intent + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, dexes); + return true; + } + + private static boolean isJustArtSupportDex(ShareDexDiffPatchInfo dexDiffPatchInfo) { + if (ShareTinkerInternals.isVmArt()) { + return false; + } + + String destMd5InDvm = dexDiffPatchInfo.destMd5InDvm; + + if (destMd5InDvm.equals("0")) { + return true; + } + + return false; + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerLoader.java new file mode 100644 index 00000000..7b1853f3 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerLoader.java @@ -0,0 +1,258 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.util.Log; + +import com.tencent.tinker.loader.app.TinkerApplication; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; + +/** + * Created by zhangshaowen on 16/3/10. + * Warning, it is special for loader classes, they can't change through tinker patch. + * thus, it's reference class must put in the tinkerPatch.dex.loader{} and the android main dex pattern through gradle + */ +public class TinkerLoader extends AbstractTinkerLoader { + private static final String TAG = "Tinker.TinkerLoader"; + + /** + * the patch info file + */ + private SharePatchInfo patchInfo; + + /** + * only main process can handle patch version change or incomplete + */ + @Override + public Intent tryLoad(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag) { + Intent resultIntent = new Intent(); + + long begin = SystemClock.elapsedRealtime(); + tryLoadPatchFilesInternal(app, tinkerFlag, tinkerLoadVerifyFlag, resultIntent); + long cost = SystemClock.elapsedRealtime() - begin; + ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost); + return resultIntent; + } + + private void tryLoadPatchFilesInternal(TinkerApplication app, int tinkerFlag, boolean tinkerLoadVerifyFlag, Intent resultIntent) { + if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) { + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE); + return; + } + //tinker + File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app); + if (patchDirectoryFile == null) { + Log.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null"); + //treat as not exist + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); + return; + } + String patchDirectoryPath = patchDirectoryFile.getAbsolutePath(); + + //check patch directory whether exist + if (!patchDirectoryFile.exists()) { + Log.w(TAG, "tryLoadPatchFiles:patch dir not exist:" + patchDirectoryPath); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); + return; + } + + //tinker/patch.info + File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath); + + //check patch info file whether exist + if (!patchInfoFile.exists()) { + Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath()); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST); + return; + } + //old = 641e634c5b8f1649c75caf73794acbdf + //new = 2c150d8560334966952678930ba67fa8 + File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath); + + patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); + if (patchInfo == null) { + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); + return; + } + + String oldVersion = patchInfo.oldVersion; + String newVersion = patchInfo.newVersion; + + if (oldVersion == null || newVersion == null) { + //it is nice to clean patch + Log.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted"); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); + return; + } + + resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion); + resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion); + + boolean mainProcess = ShareTinkerInternals.isInMainProcess(app); + boolean versionChanged = !(oldVersion.equals(newVersion)); + + String version = oldVersion; + if (versionChanged && mainProcess) { + version = newVersion; + } + if (ShareTinkerInternals.isNullOrNil(version)) { + Log.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart"); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK); + return; + } + + //patch-641e634c + String patchName = SharePatchFileUtil.getPatchVersionDirectory(version); + + //tinker/patch.info/patch-641e634c + String patchVersionDirectory = patchDirectoryPath + "/" + patchName; + File patchVersionDirectoryFile = new File(patchVersionDirectory); + + if (!patchVersionDirectoryFile.exists()) { + Log.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound"); + //we may delete patch info file + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST); + return; + } + + //tinker/patch.info/patch-641e634c/patch-641e634c.apk + File patchVersionFile = new File(patchVersionDirectoryFile.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(version)); + + if (!patchVersionFile.exists()) { + Log.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound"); + //we may delete patch info file + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST); + return; + } + + ShareSecurityCheck securityCheck = new ShareSecurityCheck(app); + + int returnCode = ShareTinkerInternals.checkSignatureAndTinkerID(app, patchVersionFile, securityCheck); + if (returnCode != 0) { + Log.w(TAG, "tryLoadPatchFiles:checkSignatureAndTinkerID"); + resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); + return; + } + + resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent()); + + final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag); + + if (isEnabledForDex) { + //tinker/patch.info/patch-641e634c/dex + boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); + if (!dexCheck) { + //file not found, do not load patch + Log.w(TAG, "tryLoadPatchFiles:dex check fail"); + return; + } + } + + final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag); + + if (isEnabledForNativeLib) { + //tinker/patch.info/patch-641e634c/lib + boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); + if (!libCheck) { + //file not found, do not load patch + Log.w(TAG, "tryLoadPatchFiles:native lib check fail"); + return; + } + } + + //check resource + final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag); + Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource); + if (isEnabledForResource) { + boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent); + if (!resourceCheck) { + //file not found, do not load patch + Log.w(TAG, "tryLoadPatchFiles:resource check fail"); + return; + } + } + //we should first try rewrite patch info file, if there is a error, we can't load jar + if (mainProcess && versionChanged) { + patchInfo.oldVersion = version; + //update old version to new + if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) { + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL); + Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted"); + return; + } + } + if (!checkSafeModeCount(app)) { + resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, new TinkerRuntimeException("checkSafeModeCount fail")); + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION); + Log.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail"); + return; + } + //now we can load patch jar + if (isEnabledForDex) { + boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent); + if (!loadTinkerJars) { + Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail"); + return; + } + } + + //now we can load patch resource + if (isEnabledForResource) { + boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent); + if (!loadTinkerResources) { + Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail"); + return; + } + } + //all is ok! + ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK); + Log.i(TAG, "tryLoadPatchFiles: load end, ok!"); + return; + } + + private boolean checkSafeModeCount(TinkerApplication application) { + String processName = ShareTinkerInternals.getProcessName(application); + String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName; + //each process have its own SharedPreferences file + SharedPreferences sp = application.getSharedPreferences(preferName, Context.MODE_PRIVATE); + int count = sp.getInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0); + Log.w(TAG, "tinker safe mode preferName:" + preferName + " count:" + count); + if (count >= ShareConstants.TINKER_SAFE_MODE_MAX_COUNT) { + sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit(); + return false; + } + application.setUseSafeMode(true); + count++; + sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, count).commit(); + Log.w(TAG, "after tinker safe mode count:" + count); + return true; + } + + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourceLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourceLoader.java new file mode 100644 index 00000000..3976ffe5 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourceLoader.java @@ -0,0 +1,124 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareResPatchInfo; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; + +import java.io.File; + +/** + * Created by liangwenxiang on 2016/4/14. + */ +public class TinkerResourceLoader { + + protected static final String RESOURCE_META_FILE = ShareConstants.RES_META_FILE; + protected static final String RESOURCE_FILE = ShareConstants.RES_NAME; + protected static final String RESOURCE_PATH = ShareConstants.RES_PATH; + private static final String TAG = "Tinker.ResourceLoader"; + private static ShareResPatchInfo resPatchInfo = new ShareResPatchInfo(); + + + private TinkerResourceLoader() { + } + + /** + * Load tinker resources + */ + public static boolean loadTinkerResources(boolean tinkerLoadVerifyFlag, String directory, Intent intentResult) { + if (resPatchInfo == null || resPatchInfo.resArscMd5 == null) { + return true; + } + String resourceString = directory + "/" + RESOURCE_PATH + "/" + RESOURCE_FILE; + File resourceFile = new File(resourceString); + + if (tinkerLoadVerifyFlag) { + long start = System.currentTimeMillis(); + if (!SharePatchFileUtil.checkResourceArscMd5(resourceFile, resPatchInfo.resArscMd5)) { + Log.e(TAG, "Failed to load resource file, path: " + resourceFile.getPath() + ", expect md5: " + resPatchInfo.resArscMd5); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH); + return false; + } + Log.i(TAG, "verify resource file:" + resourceFile.getPath() + ", md5 use time: " + (System.currentTimeMillis() - start)); + } + try { + TinkerResourcePatcher.monkeyPatchExistingResources(resourceString); + } catch (Throwable e) { + Log.e(TAG, "install resources failed", e); + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION); + return false; + } + return true; + } + + /** + * resource file exist? + * fast check, only check whether exist + * + * @param directory + * @return boolean + */ + public static boolean checkComplete(Context context, String directory, ShareSecurityCheck securityCheck, Intent intentResult) { + String meta = securityCheck.getMetaContentMap().get(RESOURCE_META_FILE); + //not found resource + if (meta == null) { + return true; + } + //only parse first line for faster + ShareResPatchInfo.parseResPatchInfoFirstLine(meta, resPatchInfo); + + if (resPatchInfo.resArscMd5 == null) { + return true; + } + if (!ShareResPatchInfo.checkResPatchInfo(resPatchInfo)) { + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); + return false; + } + String resourcePath = directory + "/" + RESOURCE_PATH + "/"; + + File resourceDir = new File(resourcePath); + + if (!resourceDir.exists() || !resourceDir.isDirectory()) { + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST); + return false; + } + + File resourceFile = new File(resourcePath + RESOURCE_FILE); + if (!resourceFile.exists()) { + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST); + return false; + } + try { + TinkerResourcePatcher.isResourceCanPatch(context); + } catch (Throwable e) { + Log.e(TAG, "resource hook check failed.", e); + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION); + return false; + } + return true; + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java new file mode 100644 index 00000000..81aaa4e0 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.util.ArrayMap; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.KITKAT; + +class TinkerResourcePatcher { + // original value + private static Collection> references; + + private static AssetManager newAssetManager = null; + private static Method addAssetPathMethod = null; + private static Method ensureStringBlocksMethod = null; + private static Field assetsFiled = null; + private static Field resourcesImplFiled = null; + + public static void isResourceCanPatch(Context context) throws Throwable { + /* + (Note: the resource directory is *also* inserted into the loadedApk in + monkeyPatchApplication) + The code seems to perform this: + File externalResourceFile = + + AssetManager newAssetManager = new AssetManager(); + newAssetManager.addAssetPath(externalResourceFile) + + // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm + // in L, so we do it unconditionally. + newAssetManager.ensureStringBlocks(); + + // Find the singleton instance of ResourcesManager + ResourcesManager resourcesManager = ResourcesManager.getInstance(); + + // Iterate over all known Resources objects + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + for (WeakReference wr : resourcesManager.mActiveResources.values()) { + Resources resources = wr.get(); + // Set the AssetManager of the Resources instance to our brand new one + resources.mAssets = newAssetManager; + resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); + } + } + + // Also, for each context, call getTheme() to get the current theme; null out its + // mTheme field, then invoke initializeTheme() to force it to be recreated (with the + // new asset manager!) + + */ + // Create a new AssetManager instance and point it to the resources installed under + // /sdcard + newAssetManager = AssetManager.class.getConstructor().newInstance(); + addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); + addAssetPathMethod.setAccessible(true); + + // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm + // in L, so we do it unconditionally. + ensureStringBlocksMethod = AssetManager.class.getDeclaredMethod("ensureStringBlocks"); + ensureStringBlocksMethod.setAccessible(true); + + // Iterate over all known Resources objects + if (SDK_INT >= KITKAT) { + //pre-N + // Find the singleton instance of ResourcesManager + Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); + Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance"); + mGetInstance.setAccessible(true); + Object resourcesManager = mGetInstance.invoke(null); + try { + Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources"); + fMActiveResources.setAccessible(true); + ArrayMap> arrayMap = + (ArrayMap>) fMActiveResources.get(resourcesManager); + references = arrayMap.values(); + } catch (NoSuchFieldException ignore) { + // N moved the resources to mResourceReferences + Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences"); + mResourceReferences.setAccessible(true); + //noinspection unchecked + references = (Collection>) mResourceReferences.get(resourcesManager); + } + } else { + Class activityThread = Class.forName("android.app.ActivityThread"); + Field fMActiveResources = activityThread.getDeclaredField("mActiveResources"); + fMActiveResources.setAccessible(true); + Object thread = getActivityThread(context, activityThread); + @SuppressWarnings("unchecked") + HashMap> map = + (HashMap>) fMActiveResources.get(thread); + references = map.values(); + } + // check resource + if (references == null || references.isEmpty()) { + throw new IllegalStateException("resource references is null or empty"); + } + try { + assetsFiled = Resources.class.getDeclaredField("mAssets"); + assetsFiled.setAccessible(true); + } catch (Throwable ignore) { + // N moved the mAssets inside an mResourcesImpl field + resourcesImplFiled = Resources.class.getDeclaredField("mResourcesImpl"); + resourcesImplFiled.setAccessible(true); + } + } + + public static void monkeyPatchExistingResources(String externalResourceFile) throws Throwable { + if (externalResourceFile == null) { + return; + } + + // Create a new AssetManager instance and point it to the resources installed under + // /sdcard + + if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) { + throw new IllegalStateException("Could not create new AssetManager"); + } + + // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm + // in L, so we do it unconditionally. + ensureStringBlocksMethod.invoke(newAssetManager); + + for (WeakReference wr : references) { + Resources resources = wr.get(); + //pre-N + if (resources != null) { + // Set the AssetManager of the Resources instance to our brand new one + try { + assetsFiled.set(resources, newAssetManager); + } catch (Throwable ignore) { + //N + Object resourceImpl = resourcesImplFiled.get(resources); + Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets"); + implAssets.setAccessible(true); + implAssets.set(resourceImpl, newAssetManager); + } + + resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); + } + } + } + + private static Object getActivityThread(Context context, + Class activityThread) { + try { + if (activityThread == null) { + activityThread = Class.forName("android.app.ActivityThread"); + } + Method m = activityThread.getMethod("currentActivityThread"); + m.setAccessible(true); + Object currentActivityThread = m.invoke(null); + if (currentActivityThread == null && context != null) { + // In older versions of Android (prior to frameworks/base 66a017b63461a22842) + // the currentActivityThread was built on thread locals, so we'll need to try + // even harder + Field mLoadedApk = context.getClass().getField("mLoadedApk"); + mLoadedApk.setAccessible(true); + Object apk = mLoadedApk.get(context); + Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread"); + mActivityThreadField.setAccessible(true); + currentActivityThread = mActivityThreadField.get(apk); + } + return currentActivityThread; + } catch (Throwable ignore) { + return null; + } + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerRuntimeException.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerRuntimeException.java new file mode 100644 index 00000000..018bbc66 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerRuntimeException.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +/** + * Created by zhangshaowen on 16/7/8. + */ +public class TinkerRuntimeException extends RuntimeException { + private static final String TINKER_RUNTIME_EXCEPTION_PREFIX = "Tinker Exception:"; + private static final long serialVersionUID = 1L; + + public TinkerRuntimeException(String detailMessage) { + super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage); + } + + public TinkerRuntimeException(String detailMessage, Throwable throwable) { + super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage, throwable); + } + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerSoLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerSoLoader.java new file mode 100644 index 00000000..c495465b --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerSoLoader.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import android.content.Intent; + +import com.tencent.tinker.loader.shareutil.ShareBsDiffPatchInfo; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + + +/** + * Created by zhangshaowen on 16/3/8. + */ + +/** + * check the complete of the dex files + * pre-load patch dex files + * we won't load patch library directly! + */ +public class TinkerSoLoader { + protected static final String SO_MEAT_FILE = ShareConstants.SO_META_FILE; + protected static final String SO_PATH = ShareConstants.SO_PATH; + private static final String TAG = "Tinker.TinkerSoLoader"; + + /** + * all the library files in meta file exist? + * fast check, only check whether exist + * + * @param directory + * @return boolean + */ + public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, Intent intentResult) { + String meta = securityCheck.getMetaContentMap().get(SO_MEAT_FILE); + //not found lib + if (meta == null) { + return true; + } + ArrayList libraryList = new ArrayList<>(); + ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, libraryList); + + if (libraryList.isEmpty()) { + return true; + } + + //tinker//patch-641e634c/lib + String libraryPath = directory + "/" + SO_PATH + "/"; + + HashMap libs = new HashMap<>(); + + for (ShareBsDiffPatchInfo info : libraryList) { + if (!ShareBsDiffPatchInfo.checkDiffPatchInfo(info)) { + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED); + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); + return false; + } + String middle = info.path + "/" + info.name; + + //unlike dex, keep the original structure + libs.put(middle, info.md5); + } + + File libraryDir = new File(libraryPath); + + if (!libraryDir.exists() || !libraryDir.isDirectory()) { + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST); + return false; + } + + //fast check whether there is any dex files missing + for (String relative : libs.keySet()) { + File libFile = new File(libraryPath + relative); + if (!libFile.exists()) { + ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST); + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH, libFile.getAbsolutePath()); + return false; + } + } + + //if is ok, add to result intent + intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_LIBS_PATH, libs); + return true; + } + + +} \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerTestDexLoad.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerTestDexLoad.java new file mode 100644 index 00000000..82b8f1c8 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerTestDexLoad.java @@ -0,0 +1,24 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +/** + * Created by zhangshaowen on 16/9/18. + */ +public class TinkerTestDexLoad { + public static boolean isPatch = false; +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLifeCycle.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLifeCycle.java new file mode 100644 index 00000000..3b67094d --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLifeCycle.java @@ -0,0 +1,66 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.app; + +/** + * Created by zhangshaowen on 16/3/8. + */ + + +import android.app.Application; +import android.content.Context; +import android.content.res.Configuration; + +/** + * This interface is used to delegate calls from main Application object. + * + * Implementations of this interface must have a one-argument constructor that takes + * an argument of type {@link Application}. + */ +public interface ApplicationLifeCycle { + + /** + * Same as {@link Application#onCreate()}. + */ + void onCreate(); + + /** + * Same as {@link Application#onLowMemory()}. + */ + void onLowMemory(); + + /** + * Same as {@link Application#onTrimMemory(int level)}. + * @param level + */ + void onTrimMemory(int level); + + /** + * Same as {@link Application#onTerminate()}. + */ + void onTerminate(); + + /** + * Same as {@link Application#onConfigurationChanged(Configuration newconfig)}. + */ + void onConfigurationChanged(Configuration newConfig); + + /** + * Same as {@link Application#attachBaseContext(Context context)}. + */ + void onBaseContextAttached(Context base); +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLike.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLike.java new file mode 100644 index 00000000..df3d558f --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/ApplicationLike.java @@ -0,0 +1,119 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.app; + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; + +/** + * Created by zhangshaowen on 16/7/28. + */ +public abstract class ApplicationLike implements ApplicationLifeCycle { + private final Application application; + private final Intent tinkerResultIntent; + private final long applicationStartElapsedTime; + private final long applicationStartMillisTime; + private final int tinkerFlags; + private final boolean tinkerLoadVerifyFlag; + private Resources[] resources; + private ClassLoader[] classLoader; + private AssetManager[] assetManager; + + public ApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, + long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, + Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) { + this.application = application; + this.tinkerFlags = tinkerFlags; + this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; + this.applicationStartElapsedTime = applicationStartElapsedTime; + this.applicationStartMillisTime = applicationStartMillisTime; + this.tinkerResultIntent = tinkerResultIntent; + this.resources = resources; + this.classLoader = classLoader; + this.assetManager = assetManager; + } + + public void setResources(Resources resources) { + this.resources[0] = resources; + } + + public void setAssetManager(AssetManager assetManager) { + this.assetManager[0] = assetManager; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader[0] = classLoader; + } + + public Application getApplication() { + return application; + } + + public final Intent getTinkerResultIntent() { + return tinkerResultIntent; + } + + public final int getTinkerFlags() { + return tinkerFlags; + } + + public final boolean getTinkerLoadVerifyFlag() { + return tinkerLoadVerifyFlag; + } + + public long getApplicationStartElapsedTime() { + return applicationStartElapsedTime; + } + + public long getApplicationStartMillisTime() { + return applicationStartMillisTime; + } + + @Override + public void onCreate() { + + } + + @Override + public void onLowMemory() { + + } + + @Override + public void onTrimMemory(int level) { + + } + + @Override + public void onTerminate() { + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + + } + + @Override + public void onBaseContextAttached(Context base) { + + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/DefaultApplicationLike.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/DefaultApplicationLike.java new file mode 100644 index 00000000..f0367db2 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/DefaultApplicationLike.java @@ -0,0 +1,72 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.app; + +/** + * Created by zhangshaowen on 16/3/8. + */ + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.Log; + +/** + * Empty implementation of {@link ApplicationLike}. + */ +public class DefaultApplicationLike extends ApplicationLike { + private static final String TAG = "Tinker.DefaultAppLike"; + + public DefaultApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, + long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, + Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) { + super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager); + } + + @Override + public void onCreate() { + Log.d(TAG, "onCreate"); + } + + @Override + public void onLowMemory() { + Log.d(TAG, "onLowMemory"); + } + + @Override + public void onTrimMemory(int level) { + Log.d(TAG, "onTrimMemory level:" + level); + } + + @Override + public void onTerminate() { + Log.d(TAG, "onTerminate"); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.d(TAG, "onConfigurationChanged:" + newConfig.toString()); + } + + @Override + public void onBaseContextAttached(Context base) { + Log.d(TAG, "onBaseContextAttached:"); + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java new file mode 100644 index 00000000..eb908b86 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java @@ -0,0 +1,269 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.app; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.SystemClock; + +import com.tencent.tinker.loader.TinkerLoader; +import com.tencent.tinker.loader.TinkerRuntimeException; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareIntentUtil; +import com.tencent.tinker.loader.shareutil.ShareReflectUtil; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Created by zhangshaowen on 16/3/8. + *

+ * A base class for implementing an Application that delegates to an {@link ApplicationLifeCycle} + * instance. This is used in conjunction with secondary dex files so that the logic that would + * normally live in the Application class is loaded after the secondary dexes are loaded. + */ +public abstract class TinkerApplication extends Application { + + //oh, we can use ShareConstants, because they are loader class and static final! + private static final int TINKER_DISABLE = ShareConstants.TINKER_DISABLE; + private static final String INTENT_PATCH_EXCEPTION = ShareIntentUtil.INTENT_PATCH_EXCEPTION; + private static final String TINKER_LOADER_METHOD = "tryLoad"; + /** + * tinkerFlags, which types is supported + * dex only, library only, all support + * default: TINKER_ENABLE_ALL + */ + private final int tinkerFlags; + /** + * whether verify md5 when we load dex or lib + * they store at data/data/package, and we had verity them at the :patch process. + * so we don't have to verity them every time for quicker! + * default:false + */ + private final boolean tinkerLoadVerifyFlag; + private final String delegateClassName; + private final String loaderClassName; + /** + * if we have load patch, we should use safe mode + */ + private boolean useSafeMode; + private Intent tinkerResultIntent; + + private Object delegate = null; + private Resources[] resources = new Resources[1]; + private ClassLoader[] classLoader = new ClassLoader[1]; + private AssetManager[] assetManager = new AssetManager[1]; + + private long applicationStartElapsedTime; + private long applicationStartMillisTime; + + /** + * current build. + */ + protected TinkerApplication(int tinkerFlags) { + this(tinkerFlags, "com.tencent.tinker.loader.app.DefaultApplicationLike", TinkerLoader.class.getName(), false); + } + + /** + * @param delegateClassName The fully-qualified name of the {@link ApplicationLifeCycle} class + * that will act as the delegate for application lifecycle callbacks. + */ + protected TinkerApplication(int tinkerFlags, String delegateClassName, + String loaderClassName, boolean tinkerLoadVerifyFlag) { + this.tinkerFlags = tinkerFlags; + this.delegateClassName = delegateClassName; + this.loaderClassName = loaderClassName; + this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; + + } + + protected TinkerApplication(int tinkerFlags, String delegateClassName) { + this(tinkerFlags, delegateClassName, TinkerLoader.class.getName(), false); + } + + private Object createDelegate() { + try { + // Use reflection to create the delegate so it doesn't need to go into the primary dex. + // And we can also patch it + Class delegateClass = Class.forName(delegateClassName, false, getClassLoader()); + Constructor constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class, + Intent.class, Resources[].class, ClassLoader[].class, AssetManager[].class); + return constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag, + applicationStartElapsedTime, applicationStartMillisTime, + tinkerResultIntent, resources, classLoader, assetManager); + } catch (Throwable e) { + throw new TinkerRuntimeException("createDelegate failed", e); + } + } + + private synchronized void ensureDelegate() { + if (delegate == null) { + delegate = createDelegate(); + } + } + + /** + * Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been + * called but before the delegate is created. Implementors should be very careful what they do + * here since {@link android.app.Application#onCreate} will not have yet been called. + */ + private void onBaseContextAttached(Context base) { + applicationStartElapsedTime = SystemClock.elapsedRealtime(); + applicationStartMillisTime = System.currentTimeMillis(); + loadTinker(); + ensureDelegate(); + try { + Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class); + method.invoke(delegate, base); + } catch (Throwable t) { + throw new TinkerRuntimeException("onBaseContextAttached method not found", t); + } + //reset save mode + if (useSafeMode) { + String processName = ShareTinkerInternals.getProcessName(this); + String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName; + SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE); + sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit(); + } + } + + @Override + protected final void attachBaseContext(Context base) { + super.attachBaseContext(base); + onBaseContextAttached(base); + } + + private void loadTinker() { + //disable tinker, not need to install + if (tinkerFlags == TINKER_DISABLE) { + return; + } + tinkerResultIntent = new Intent(); + try { + //reflect tinker loader, because loaderClass may be define by user! + Class tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader()); + + Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class); + Constructor constructor = tinkerLoadClass.getConstructor(); + tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag); + } catch (Throwable e) { + //has exception, put exception error code + ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION); + tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e); + } + } + + private void delegateMethod(String methodName) { + if (delegate != null) { + try { + Method method = ShareReflectUtil.findMethod(delegate, methodName, new Class[0]); + method.invoke(delegate, new Object[0]); + } catch (Throwable t) { + throw new TinkerRuntimeException(String.format("%s method not found", methodName), t); + } + } + } + + @Override + public final void onCreate() { + super.onCreate(); + ensureDelegate(); + delegateMethod("onCreate"); + } + + @Override + public final void onTerminate() { + super.onTerminate(); + delegateMethod("onTerminate"); + } + + @Override + public final void onLowMemory() { + super.onLowMemory(); + delegateMethod("onLowMemory"); + } + + private void delegateTrimMemory(int level) { + if (delegate != null) { + try { + Method method = ShareReflectUtil.findMethod(delegate, "onTrimMemory", int.class); + method.invoke(delegate, level); + } catch (Throwable t) { + throw new TinkerRuntimeException("onTrimMemory method not found", t); + } + } + } + + @TargetApi(14) + @Override + public final void onTrimMemory(int level) { + super.onTrimMemory(level); + delegateTrimMemory(level); + } + + private void delegateConfigurationChanged(Configuration newConfig) { + if (delegate != null) { + try { + Method method = ShareReflectUtil.findMethod(delegate, "onConfigurationChanged", Configuration.class); + method.invoke(delegate, newConfig); + } catch (Throwable t) { + throw new TinkerRuntimeException("onConfigurationChanged method not found", t); + } + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + delegateConfigurationChanged(newConfig); + } + + @Override + public Resources getResources() { + if (resources[0] != null) { + return resources[0]; + } + return super.getResources(); + } + + @Override + public ClassLoader getClassLoader() { + if (classLoader[0] != null) { + return classLoader[0]; + } + return super.getClassLoader(); + } + + @Override + public AssetManager getAssets() { + if (assetManager[0] != null) { + return assetManager[0]; + } + return super.getAssets(); + } + + public void setUseSafeMode(boolean useSafeMode) { + this.useSafeMode = useSafeMode; + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareBsDiffPatchInfo.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareBsDiffPatchInfo.java new file mode 100644 index 00000000..5488e4e8 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareBsDiffPatchInfo.java @@ -0,0 +1,95 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import java.util.ArrayList; + +/** + * patch via bsdiff + * Created by zhangshaowen on 16/3/16. + */ +public class ShareBsDiffPatchInfo { + public String name; + public String md5; + public String rawCrc; + public String patchMd5; + + public String path; + + public ShareBsDiffPatchInfo(String name, String md5, String path, String raw, String patch) { + // TODO Auto-generated constructor stub + this.name = name; + this.md5 = md5; + this.rawCrc = raw; + this.patchMd5 = patch; + this.path = path; + } + + public static void parseDiffPatchInfo(String meta, ArrayList diffList) { + if (meta == null || meta.length() == 0) { + return; + } + String[] lines = meta.split("\n"); + for (final String line : lines) { + if (line == null || line.length() <= 0) { + continue; + } + final String[] kv = line.split(",", 5); + if (kv == null || kv.length < 5) { + continue; + } + // key + final String name = kv[0].trim(); + final String path = kv[1].trim(); + final String md5 = kv[2].trim(); + final String rawCrc = kv[3].trim(); + final String patchMd5 = kv[4].trim(); + + ShareBsDiffPatchInfo dexInfo = new ShareBsDiffPatchInfo(name, md5, path, rawCrc, patchMd5); + diffList.add(dexInfo); + } + + } + + public static boolean checkDiffPatchInfo(ShareBsDiffPatchInfo info) { + if (info == null) { + return false; + } + String name = info.name; + String md5 = info.md5; + if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { + return false; + } + + return true; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(name); + sb.append(","); + sb.append(path); + sb.append(","); + sb.append(md5); + sb.append(","); + sb.append(rawCrc); + sb.append(","); + sb.append(patchMd5); + return sb.toString(); + } +} \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareConstants.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareConstants.java new file mode 100644 index 00000000..8f53fcb6 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareConstants.java @@ -0,0 +1,170 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +/** + * Created by zhangshaowen on 16/3/24. + */ +public class ShareConstants { + public static final int BUFFER_SIZE = 16384; + public static final int MD5_LENGTH = 32; + public static final int MD5_FILE_BUF_LENGTH = 1024 * 100; + + public static final int MAX_EXTRACT_ATTEMPTS = 2; + + public static final String TINKER_ID = "TINKER_ID"; + public static final String NEW_TINKER_ID = "NEW_TINKER_ID"; + + public static final String OLD_VERSION = "old"; + public static final String NEW_VERSION = "new"; + public static final String PATCH_BASE_NAME = "patch-"; + public static final String PATCH_SUFFIX = ".apk"; + + public static final String PACKAGE_META_FILE = "assets/package_meta.txt"; + + public static final String SO_META_FILE = "assets/so_meta.txt"; + public static final String SO_PATH = "lib"; + + // If you changed this value, please change the same value in TypedValue, too. + public static final String DEX_SMALLPATCH_INFO_FILE = "smallpatch_info.ddextra"; + + public static final String DEX_META_FILE = "assets/dex_meta.txt"; + public static final String DEX_PATH = "dex"; + public static final String DEX_OPTIMIZE_PATH = "odex"; + public static final String DEX_SUFFIX = ".dex"; + public static final String JAR_SUFFIX = ".jar"; + public static final String CHECK_DEX_INSTALL_FAIL = "checkDexInstall failed"; + + public static final String RES_META_FILE = "assets/res_meta.txt"; + public static final String RES_ARSC = "resources.arsc"; + public static final String RES_MANIFEST = "AndroidManifest.xml"; + public static final String RES_TITLE = "resources_out.zip"; + public static final String RES_PATH = "res"; + public static final String RES_NAME = "resources.apk"; + public static final String RES_ADD_TITLE = "add:"; + public static final String RES_MOD_TITLE = "modify:"; + public static final String RES_LARGE_MOD_TITLE = "large modify:"; + public static final String RES_DEL_TITLE = "delete:"; + public static final String RES_PATTERN_TITLE = "pattern:"; + + public static final String DEXMODE_RAW = "raw"; + public static final String DEXMODE_JAR = "jar"; + public static final String DEX_IN_JAR = "classes.dex"; + + public static final String PATCH_DIRECTORY_NAME = "tinker"; + public static final String PATCH_INFO_NAME = "patch.info"; + public static final String PATCH_INFO_LOCK_NAME = "info.lock"; + + public static final String META_SUFFIX = "meta.txt"; + + /** + * multi process share + */ + public static final String TINKER_SHARE_PREFERENCE_CONFIG = "tinker_share_config"; + public static final String TINKER_ENABLE_CONFIG = "tinker_enable"; + + /** + * only for each process + */ + public static final String TINKER_OWN_PREFERENCE_CONFIG = "tinker_own_config_"; + public static final String TINKER_SAFE_MODE_COUNT = "safe_mode_count"; + public static final int TINKER_SAFE_MODE_MAX_COUNT = 3; + + + /** + * notification id, use to Increasing the patch process priority + * your app shouldn't use the same notification id. + * if you want to define it, use {@code TinkerPatchService.setTinkerNotificationId} + */ + public static final int TINKER_PATCH_SERVICE_NOTIFICATION = -1119860829; + + //resource type + public static final int TYPE_PATCH_FILE = 1; + public static final int TYPE_PATCH_INFO = 2; + public static final int TYPE_DEX = 3; + /** + * for art small dex + */ + public static final int TYPE_DEX_FOR_ART = 4; + public static final int TYPE_DEX_OPT = 5; + public static final int TYPE_LIBRARY = 6; + public static final int TYPE_RESOURCE = 7; + + + public static final int TINKER_DISABLE = 0x00; + public static final int TINKER_DEX_MASK = 0x01; + public static final int TINKER_NATIVE_LIBRARY_MASK = 0x02; + public static final int TINKER_RESOURCE_MASK = 0x04; + public static final int TINKER_DEX_AND_LIBRARY = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK; + public static final int TINKER_ENABLE_ALL = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_RESOURCE_MASK; + + //load error code + public static final int ERROR_LOAD_OK = 0; + public static final int ERROR_LOAD_DISABLE = -1; + public static final int ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST = -2; + public static final int ERROR_LOAD_PATCH_INFO_NOT_EXIST = -3; + public static final int ERROR_LOAD_PATCH_INFO_CORRUPTED = -4; + public static final int ERROR_LOAD_PATCH_INFO_BLANK = -5; + public static final int ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST = -6; + public static final int ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST = -7; + public static final int ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL = -9; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST = -10; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST = -11; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST = -12; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL = -13; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH = -14; + public static final int ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION = -15; + public static final int ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST = -16; + public static final int ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST = -17; + public static final int ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL = -18; + public static final int ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION = -19; + //resource + public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST = -21; + public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST = -22; + public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION = -23; + public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH = -24; + public static final int ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION = -25; + + public static final int ERROR_LOAD_GET_INTENT_FAIL = -10000; + + //load exception code + //recover error code + public static final int ERROR_LOAD_EXCEPTION_UNKNOWN = -1; + public static final int ERROR_LOAD_EXCEPTION_DEX = -2; + public static final int ERROR_LOAD_EXCEPTION_RESOURCE = -3; + public static final int ERROR_LOAD_EXCEPTION_UNCAUGHT = -4; + + + //recover error code + public static final int ERROR_PATCH_OK = 0; + public static final int ERROR_PATCH_DISABLE = -1; + public static final int ERROR_PATCH_NOTEXIST = -2; + public static final int ERROR_PATCH_RUNNING = -3; + public static final int ERROR_PATCH_INSERVICE = -4; + + //package check error code + public static final int ERROR_PACKAGE_CHECK_OK = 0; + public static final int ERROR_PACKAGE_CHECK_SIGNATURE_FAIL = -1; + public static final int ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = -2; + public static final int ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED = -3; + public static final int ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED = -4; + public static final int ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = -5; + public static final int ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = -6; + public static final int ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = -7; + public static final int ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED = -8; + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareDexDiffPatchInfo.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareDexDiffPatchInfo.java new file mode 100644 index 00000000..a623f195 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareDexDiffPatchInfo.java @@ -0,0 +1,128 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import com.tencent.tinker.loader.TinkerRuntimeException; + +import java.util.ArrayList; + +/** + * Created by zhangshaowen on 16/4/11. + */ +public class ShareDexDiffPatchInfo { + public final String rawName; + public final String destMd5InDvm; + public final String destMd5InArt; + public final String oldDexCrC; + public final String dexDiffMd5; + + public final String path; + + public final String dexMode; + + public final boolean isJarMode; + + /** + * if it is jar mode, and the name is end of .dex, we should repackage it with zip, with renaming name.dex.jar + */ + public final String realName; + + + public ShareDexDiffPatchInfo(String name, String path, String destMd5InDvm, String destMd5InArt, String dexDiffMd5, String oldDexCrc, String dexMode) { + // TODO Auto-generated constructor stub + this.rawName = name; + this.path = path; + this.destMd5InDvm = destMd5InDvm; + this.destMd5InArt = destMd5InArt; + this.dexDiffMd5 = dexDiffMd5; + this.oldDexCrC = oldDexCrc; + this.dexMode = dexMode; + if (dexMode.equals(ShareConstants.DEXMODE_JAR)) { + this.isJarMode = true; + if (SharePatchFileUtil.isRawDexFile(name)) { + realName = name + ShareConstants.JAR_SUFFIX; + } else { + realName = name; + } + } else if (dexMode.equals(ShareConstants.DEXMODE_RAW)) { + this.isJarMode = false; + this.realName = name; + } else { + throw new TinkerRuntimeException("can't recognize dex mode:" + dexMode); + } + } + + public static void parseDexDiffPatchInfo(String meta, ArrayList dexList) { + if (meta == null || meta.length() == 0) { + return; + } + String[] lines = meta.split("\n"); + for (final String line : lines) { + if (line == null || line.length() <= 0) { + continue; + } + final String[] kv = line.split(",", 7); + if (kv == null || kv.length < 7) { + continue; + } + + // key + final String name = kv[0].trim(); + final String path = kv[1].trim(); + final String destMd5InDvm = kv[2].trim(); + final String destMd5InArt = kv[3].trim(); + final String dexDiffMd5 = kv[4].trim(); + final String oldDexCrc = kv[5].trim(); + final String dexMode = kv[6].trim(); + + ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt, dexDiffMd5, oldDexCrc, dexMode); + dexList.add(dexInfo); + } + + } + + public static boolean checkDexDiffPatchInfo(ShareDexDiffPatchInfo info) { + if (info == null) { + return false; + } + String name = info.rawName; + String md5 = (ShareTinkerInternals.isVmArt() ? info.destMd5InArt : info.destMd5InDvm); + if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(rawName); + sb.append(","); + sb.append(path); + sb.append(","); + sb.append(destMd5InDvm); + sb.append(","); + sb.append(destMd5InArt); + sb.append(","); + sb.append(oldDexCrC); + sb.append(","); + sb.append(dexDiffMd5); + sb.append(","); + sb.append(dexMode); + return sb.toString(); + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareFileLockHelper.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareFileLockHelper.java new file mode 100644 index 00000000..63c266a8 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareFileLockHelper.java @@ -0,0 +1,85 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.util.Log; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileLock; + +/** + * Created by zhangshaowen on 16/6/3. + */ +public class ShareFileLockHelper implements Closeable { + public static final int MAX_LOCK_ATTEMPTS = 3; + public static final int LOCK_WAIT_EACH_TIME = 10; + private static final String TAG = "Tinker.FileLockHelper"; + private final FileOutputStream outputStream; + private final FileLock fileLock; + + private ShareFileLockHelper(File lockFile) throws IOException { + outputStream = new FileOutputStream(lockFile); + + int numAttempts = 0; + boolean isGetLockSuccess; + FileLock localFileLock = null; + //just wait twice, + Exception saveException = null; + while (numAttempts < MAX_LOCK_ATTEMPTS) { + numAttempts++; + try { + localFileLock = outputStream.getChannel().lock(); + isGetLockSuccess = (localFileLock != null); + if (isGetLockSuccess) { + break; + } + //it can just sleep 0, afraid of cpu scheduling + Thread.sleep(LOCK_WAIT_EACH_TIME); + + } catch (Exception e) { +// e.printStackTrace(); + saveException = e; + Log.e(TAG, "getInfoLock Thread failed time:" + LOCK_WAIT_EACH_TIME); + } + } + + if (localFileLock == null) { + throw new IOException("Tinker Exception:FileLockHelper lock file failed: " + lockFile.getAbsolutePath(), saveException); + } + fileLock = localFileLock; + } + + public static ShareFileLockHelper getFileLock(File lockFile) throws IOException { + return new ShareFileLockHelper(lockFile); + } + + @Override + public void close() throws IOException { + try { + if (fileLock != null) { + fileLock.release(); + } + } finally { + if (outputStream != null) { + outputStream.close(); + } + } + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareIntentUtil.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareIntentUtil.java new file mode 100644 index 00000000..965fb4cd --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareIntentUtil.java @@ -0,0 +1,180 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.content.Intent; +import android.util.Log; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by zhangshaowen on 16/3/18. + */ +public class ShareIntentUtil { + //intent + public static final String INTENT_RETURN_CODE = "intent_return_code"; + public static final String INTENT_PATCH_OLD_VERSION = "intent_patch_old_version"; + public static final String INTENT_PATCH_NEW_VERSION = "intent_patch_new_version"; + public static final String INTENT_PATCH_MISMATCH_DEX_PATH = "intent_patch_mismatch_dex_path"; + public static final String INTENT_PATCH_MISSING_DEX_PATH = "intent_patch_missing_dex_path"; + public static final String INTENT_PATCH_DEXES_PATH = "intent_patch_dexes_path"; + public static final String INTENT_PATCH_MISMATCH_LIB_PATH = "intent_patch_mismatch_lib_path"; + public static final String INTENT_PATCH_MISSING_LIB_PATH = "intent_patch_missing_lib_path"; + public static final String INTENT_PATCH_LIBS_PATH = "intent_patch_libs_path"; + public static final String INTENT_PATCH_COST_TIME = "intent_patch_cost_time"; + public static final String INTENT_PATCH_EXCEPTION = "intent_patch_exception"; + public static final String INTENT_PATCH_PACKAGE_PATCH_CHECK = "intent_patch_package_patch_check"; + public static final String INTENT_PATCH_PACKAGE_CONFIG = "intent_patch_package_config"; + private static final String TAG = "ShareIntentUtil"; + + public static void setIntentReturnCode(Intent intent, int code) { + intent.putExtra(INTENT_RETURN_CODE, code); + } + + public static int getIntentReturnCode(Intent intent) { + return getIntExtra(intent, INTENT_RETURN_CODE, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); + } + + public static void setIntentPatchCostTime(Intent intent, long cost) { + intent.putExtra(INTENT_PATCH_COST_TIME, cost); + } + + public static long getIntentPatchCostTime(Intent intent) { + return intent.getLongExtra(INTENT_PATCH_COST_TIME, 0); + } + + public static Exception getIntentPatchException(Intent intent) { + Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_EXCEPTION); + if (serializable != null) { + return (Exception) serializable; + } + return null; + } + + public static HashMap getIntentPatchDexPaths(Intent intent) { + Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_DEXES_PATH); + if (serializable != null) { + return (HashMap) serializable; + } + return null; + } + + public static HashMap getIntentPatchLibsPaths(Intent intent) { + Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_LIBS_PATH); + if (serializable != null) { + return (HashMap) serializable; + } + return null; + } + + public static HashMap getIntentPackageConfig(Intent intent) { + Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_PACKAGE_CONFIG); + if (serializable != null) { + return (HashMap) serializable; + } + return null; + } + + + public static ArrayList getStringArrayListExtra(Intent intent, String name) { + if (null == intent) { + return null; + } + ArrayList ret = null; + try { + ret = intent.getStringArrayListExtra(name); + } catch (Exception e) { + Log.e(TAG, "getStringExtra exception:" + e.getMessage()); + ret = null; + } + return ret; + } + + + public static String getStringExtra(Intent intent, String name) { + if (null == intent) { + return null; + } + String ret = null; + try { + ret = intent.getStringExtra(name); + } catch (Exception e) { + Log.e(TAG, "getStringExtra exception:" + e.getMessage()); + ret = null; + } + return ret; + } + + public static Serializable getSerializableExtra(Intent intent, String name) { + if (null == intent) { + return null; + } + Serializable ret = null; + try { + ret = intent.getSerializableExtra(name); + } catch (Exception e) { + Log.e(TAG, "getSerializableExtra exception:" + e.getMessage()); + ret = null; + } + return ret; + } + + public static int getIntExtra(Intent intent, String name, int defaultValue) { + if (null == intent) { + return defaultValue; + } + int ret = defaultValue; + try { + ret = intent.getIntExtra(name, defaultValue); + } catch (Exception e) { + Log.e(TAG, "getIntExtra exception:" + e.getMessage()); + ret = defaultValue; + } + return ret; + } + + + public static boolean getBooleanExtra(Intent intent, String name, boolean defaultValue) { + if (null == intent) { + return defaultValue; + } + boolean ret = defaultValue; + try { + ret = intent.getBooleanExtra(name, defaultValue); + } catch (Exception e) { + Log.e(TAG, "getBooleanExtra exception:" + e.getMessage()); + ret = defaultValue; + } + return ret; + } + + public static long getLongExtra(Intent intent, String name, long defaultValue) { + if (null == intent) { + return defaultValue; + } + long ret = defaultValue; + try { + ret = intent.getLongExtra(name, defaultValue); + } catch (Exception e) { + Log.e(TAG, "getIntExtra exception:" + e.getMessage()); + ret = defaultValue; + } + return ret; + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchFileUtil.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchFileUtil.java new file mode 100644 index 00000000..ddfee922 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchFileUtil.java @@ -0,0 +1,424 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + + +public class SharePatchFileUtil { + private static final String TAG = "Tinker.PatchFileUtil"; + + /** + * data dir, such as /data/data/tinker.sample.android/tinker + * @param context + * @return + */ + public static File getPatchDirectory(Context context) { + ApplicationInfo applicationInfo = context.getApplicationInfo(); + if (applicationInfo == null) { + // Looks like running on a test Context, so just return without patching. + return null; + } + + return new File(applicationInfo.dataDir, ShareConstants.PATCH_DIRECTORY_NAME); + } + + public static File getPatchInfoFile(String patchDirectory) { + return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_NAME); + } + + public static File getPatchInfoLockFile(String patchDirectory) { + return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_LOCK_NAME); + } + + public static String getPatchVersionDirectory(String version) { + if (version == null || version.length() != ShareConstants.MD5_LENGTH) { + return null; + } + + return ShareConstants.PATCH_BASE_NAME + version.substring(0, 8); + } + + public static String getPatchVersionFile(String version) { + if (version == null || version.length() != ShareConstants.MD5_LENGTH) { + return null; + } + + return getPatchVersionDirectory(version) + ShareConstants.PATCH_SUFFIX; + } + + public static boolean checkIfMd5Valid(final String object) { + if ((object == null) || (object.length() != ShareConstants.MD5_LENGTH)) { + return false; + } + return true; + } + + public static final boolean fileExists(String filePath) { + if (filePath == null) { + return false; + } + + File file = new File(filePath); + if (file.exists()) { + return true; + } + return false; + } + + /** + * get directory size + * + * @param directory + * @return + */ + public static long getFileOrDirectorySize(File directory) { + if (directory == null || !directory.exists()) { + return 0; + } + if (directory.isFile()) { + return directory.length(); + } + long totalSize = 0; + File[] fileList = directory.listFiles(); + if (fileList != null) { + for (File file : fileList) { + if (file.isDirectory()) { + totalSize = totalSize + getFileOrDirectorySize(file); + } else { + totalSize = totalSize + file.length(); + } + } + } + return totalSize; + } + + public static final boolean safeDeleteFile(File file) { + if (file == null) { + return true; + } + + Log.i(TAG, "safeDeleteFile, try to delete path: " + file.getPath()); + + if (file.exists()) { + boolean deleted = file.delete(); + if (!deleted) { + Log.e(TAG, "Failed to delete file, try to delete when exit. path: " + file.getPath()); + file.deleteOnExit(); + } + return deleted; + } + return true; + } + + public static final boolean deleteDir(String dir) { + if (dir == null) { + return false; + } + return deleteDir(new File(dir)); + + } + + public static final boolean deleteDir(File file) { + if (file == null || (!file.exists())) { + return false; + } + if (file.isFile()) { + safeDeleteFile(file); + } else if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File subFile : files) { + deleteDir(subFile); + } + safeDeleteFile(file); + } + } + return true; + } + + + /** + * Returns whether the file is a valid file. + */ + public static boolean verifyFileMd5(File file, String md5) { + if (md5 == null) { + return false; + } + String fileMd5 = getMD5(file); + + if (fileMd5 == null) { + return false; + } + + return md5.equals(fileMd5); + } + + public static boolean isRawDexFile(String fileName) { + if (fileName == null) { + return false; + } + return fileName.endsWith(ShareConstants.DEX_SUFFIX); + } + + /** + * Returns whether the dex file is a valid file. + * dex may wrap with jar + */ + public static boolean verifyDexFileMd5(File file, String md5) { + if (file == null || md5 == null) { + return false; + } + //if it is not the raw dex, we check the stream instead + String fileMd5; + + if (isRawDexFile(file.getName())) { + fileMd5 = getMD5(file); + } else { + ZipFile dexJar = null; + try { + dexJar = new ZipFile(file); + ZipEntry classesDex = dexJar.getEntry(ShareConstants.DEX_IN_JAR); + + // no code + if (null == classesDex) { + return false; + } + fileMd5 = getMD5(dexJar.getInputStream(classesDex)); + } catch (IOException e) { +// e.printStackTrace(); + return false; + } finally { + SharePatchFileUtil.closeZip(dexJar); + } + } + + return md5.equals(fileMd5); + } + + public static void copyFileUsingStream(File source, File dest) throws IOException { + FileInputStream is = null; + FileOutputStream os = null; + File parent = dest.getParentFile(); + if (parent != null && (!parent.exists())) { + parent.mkdirs(); + } + try { + is = new FileInputStream(source); + os = new FileOutputStream(dest, false); + + byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + closeQuietly(is); + closeQuietly(os); + } + } + + /** + * for faster, read and get the contents + * + * @throws IOException + */ + public static String loadDigestes(JarFile jarFile, JarEntry je) throws Exception { + InputStream bis = null; + StringBuilder sb = new StringBuilder(); + + try { + InputStream is = jarFile.getInputStream(je); + byte[] bytes = new byte[ShareConstants.BUFFER_SIZE]; + bis = new BufferedInputStream(is); + int readBytes; + while ((readBytes = bis.read(bytes)) > 0) { + sb.append(new String(bytes, 0, readBytes)); + } + } finally { + closeQuietly(bis); + } + return sb.toString(); + } + + /** + * Get the md5 for inputStream. + * This method cost less memory. It read bufLen bytes from the FileInputStream once. + * + * @param is + */ + public final static String getMD5(final InputStream is) { + if (is == null) { + return null; + } + try { + BufferedInputStream bis = new BufferedInputStream(is); + MessageDigest md = MessageDigest.getInstance("MD5"); + StringBuilder md5Str = new StringBuilder(32); + + byte[] buf = new byte[ShareConstants.MD5_FILE_BUF_LENGTH]; + int readCount; + while ((readCount = bis.read(buf)) != -1) { + md.update(buf, 0, readCount); + } + + byte[] hashValue = md.digest(); + + for (int i = 0; i < hashValue.length; i++) { + md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); + } + return md5Str.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Get the md5 for the file. call getMD5(FileInputStream is, int bufLen) inside. + * + * @param file + */ + public static String getMD5(final File file) { + if (file == null || !file.exists()) { + return null; + } + + FileInputStream fin = null; + try { + fin = new FileInputStream(file); + String md5 = getMD5(fin); + fin.close(); + return md5; + + } catch (Exception e) { + return null; + + } finally { + try { + if (fin != null) { + fin.close(); + } + } catch (IOException e) { + + } + } + } + + /** + * change the jar file path as the makeDexElements do + * + * @param path + * @param optimizedDirectory + * @return + */ + public static String optimizedPathFor(File path, File optimizedDirectory) { + String fileName = path.getName(); + if (!fileName.endsWith(ShareConstants.DEX_SUFFIX)) { + int lastDot = fileName.lastIndexOf("."); + if (lastDot < 0) { + fileName += ShareConstants.DEX_SUFFIX; + } else { + StringBuilder sb = new StringBuilder(lastDot + 4); + sb.append(fileName, 0, lastDot); + sb.append(ShareConstants.DEX_SUFFIX); + fileName = sb.toString(); + } + } + + File result = new File(optimizedDirectory, fileName); + return result.getPath(); + } + + /** + * Closes the given {@code Closeable}. Suppresses any IO exceptions. + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + Log.w(TAG, "Failed to close resource", e); + } + } + + public static void closeZip(ZipFile zipFile) { + try { + if (zipFile != null) { + zipFile.close(); + } + } catch (IOException e) { + Log.w(TAG, "Failed to close resource", e); + } + } + + public static boolean checkResourceArscMd5(File resOutput, String destMd5) { + ZipFile resourceZip = null; + try { + resourceZip = new ZipFile(resOutput); + ZipEntry arscEntry = resourceZip.getEntry(ShareConstants.RES_ARSC); + if (arscEntry == null) { + Log.i(TAG, "checkResourceArscMd5 resources.arsc not found"); + return false; + } + InputStream inputStream = null; + try { + inputStream = resourceZip.getInputStream(arscEntry); + String md5 = SharePatchFileUtil.getMD5(inputStream); + if (md5 != null && md5.equals(destMd5)) { + return true; + } + } finally { + SharePatchFileUtil.closeQuietly(inputStream); + } + + } catch (Throwable e) { + Log.i(TAG, "checkResourceArscMd5 throwable:" + e.getMessage()); + + } finally { + SharePatchFileUtil.closeZip(resourceZip); + } + return false; + } + + public static void ensureFileDirectory(File file) { + if (file == null) { + return; + } + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + } + +} + diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchInfo.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchInfo.java new file mode 100644 index 00000000..f787fb38 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchInfo.java @@ -0,0 +1,188 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.util.Log; + +import com.tencent.tinker.loader.TinkerRuntimeException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Created by zhangshaowen on 16/3/16. + */ +public class SharePatchInfo { + public static final int MAX_EXTRACT_ATTEMPTS = ShareConstants.MAX_EXTRACT_ATTEMPTS; + public static final String OLD_VERSION = ShareConstants.OLD_VERSION; + public static final String NEW_VERSION = ShareConstants.NEW_VERSION; + private static final String TAG = "PatchInfo"; + public String oldVersion; + public String newVersion; + + public SharePatchInfo(String oldVer, String newVew) { + // TODO Auto-generated constructor stub + this.oldVersion = oldVer; + this.newVersion = newVew; + } + + public static SharePatchInfo readAndCheckPropertyWithLock(File pathInfoFile, File lockFile) { + File lockParentFile = lockFile.getParentFile(); + if (!lockParentFile.exists()) { + lockParentFile.mkdirs(); + } + + SharePatchInfo patchInfo; + ShareFileLockHelper fileLock = null; + try { + fileLock = ShareFileLockHelper.getFileLock(lockFile); + patchInfo = readAndCheckProperty(pathInfoFile); + } catch (Exception e) { + throw new TinkerRuntimeException("readAndCheckPropertyWithLock fail", e); + } finally { + try { + if (fileLock != null) { + fileLock.close(); + } + } catch (IOException e) { + Log.i(TAG, "releaseInfoLock error", e); + } + } + + return patchInfo; + } + + public static boolean rewritePatchInfoFileWithLock(File pathInfoFile, SharePatchInfo info, File lockFile) { + File lockParentFile = lockFile.getParentFile(); + if (!lockParentFile.exists()) { + lockParentFile.mkdirs(); + } + boolean rewriteSuccess; + ShareFileLockHelper fileLock = null; + try { + fileLock = ShareFileLockHelper.getFileLock(lockFile); + rewriteSuccess = rewritePatchInfoFile(pathInfoFile, info); + } catch (Exception e) { + throw new TinkerRuntimeException("rewritePatchInfoFileWithLock fail", e); + } finally { + try { + if (fileLock != null) { + fileLock.close(); + } + } catch (IOException e) { + Log.i(TAG, "releaseInfoLock error", e); + } + + } + return rewriteSuccess; + } + + private static SharePatchInfo readAndCheckProperty(File pathInfoFile) { + boolean isReadPatchSuccessful = false; + int numAttempts = 0; + String oldVer = null; + String newVer = null; + + while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isReadPatchSuccessful) { + numAttempts++; + Properties properties = new Properties(); + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(pathInfoFile); + properties.load(inputStream); + oldVer = properties.getProperty(OLD_VERSION); + newVer = properties.getProperty(NEW_VERSION); + } catch (IOException e) { + e.printStackTrace(); + } finally { + SharePatchFileUtil.closeQuietly(inputStream); + } + + if (oldVer == null || newVer == null) { + continue; + } + //oldver may be "" or 32 md5 + if ((!oldVer.equals("") && !SharePatchFileUtil.checkIfMd5Valid(oldVer)) || !SharePatchFileUtil.checkIfMd5Valid(newVer)) { + Log.w(TAG, "path info file corrupted:" + pathInfoFile.getAbsolutePath()); + continue; + } else { + isReadPatchSuccessful = true; + } + } + + if (isReadPatchSuccessful) { + return new SharePatchInfo(oldVer, newVer); + } + + return null; + } + + private static boolean rewritePatchInfoFile(File pathInfoFile, SharePatchInfo info) { + if (pathInfoFile == null || info == null) { + return false; + } + Log.i(TAG, "rewritePatchInfoFile file path:" + + pathInfoFile.getAbsolutePath() + + " , oldVer:" + + info.oldVersion + + ", newVer:" + + info.newVersion); + + boolean isWritePatchSuccessful = false; + int numAttempts = 0; + + File parentFile = pathInfoFile.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + + while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isWritePatchSuccessful) { + numAttempts++; + + Properties newProperties = new Properties(); + newProperties.put(OLD_VERSION, info.oldVersion); + newProperties.put(NEW_VERSION, info.newVersion); + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(pathInfoFile, false); + String comment = "from old version:" + info.oldVersion + " to new version:" + info.newVersion; + newProperties.store(outputStream, comment); + } catch (Exception e) { + e.printStackTrace(); + } finally { + SharePatchFileUtil.closeQuietly(outputStream); + } + + SharePatchInfo tempInfo = readAndCheckProperty(pathInfoFile); + + isWritePatchSuccessful = tempInfo != null && tempInfo.oldVersion.equals(info.oldVersion) && tempInfo.newVersion.equals(info.newVersion); + if (!isWritePatchSuccessful) { + pathInfoFile.delete(); + } + } + if (isWritePatchSuccessful) { + return true; + } + + return false; + } + + +} \ No newline at end of file diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareReflectUtil.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareReflectUtil.java new file mode 100644 index 00000000..28f0295a --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareReflectUtil.java @@ -0,0 +1,128 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Created by zhangshaowen on 16/8/22. + */ +public class ShareReflectUtil { + + /** + * Locates a given field anywhere in the class inheritance hierarchy. + * + * @param instance an object to search the field into. + * @param name field name + * @return a field object + * @throws NoSuchFieldException if the field cannot be located + */ + public static Field findField(Object instance, String name) throws NoSuchFieldException { + for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + + if (!field.isAccessible()) { + field.setAccessible(true); + } + + return field; + } catch (NoSuchFieldException e) { + // ignore and search next + } + } + + throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); + } + + public static Field findField(Class originClazz, String name) throws NoSuchFieldException { + for (Class clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) { + try { + Field field = clazz.getDeclaredField(name); + + if (!field.isAccessible()) { + field.setAccessible(true); + } + + return field; + } catch (NoSuchFieldException e) { + // ignore and search next + } + } + + throw new NoSuchFieldException("Field " + name + " not found in " + originClazz); + } + + /** + * Locates a given method anywhere in the class inheritance hierarchy. + * + * @param instance an object to search the method into. + * @param name method name + * @param parameterTypes method parameter types + * @return a method object + * @throws NoSuchMethodException if the method cannot be located + */ + public static Method findMethod(Object instance, String name, Class... parameterTypes) + throws NoSuchMethodException { + for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { + try { + Method method = clazz.getDeclaredMethod(name, parameterTypes); + + if (!method.isAccessible()) { + method.setAccessible(true); + } + + return method; + } catch (NoSuchMethodException e) { + // ignore and search next + } + } + + throw new NoSuchMethodException("Method " + + name + + " with parameters " + + Arrays.asList(parameterTypes) + + " not found in " + instance.getClass()); + } + + /** + * Replace the value of a field containing a non null array, by a new array containing the + * elements of the original array plus the elements of extraElements. + * + * @param instance the instance whose field is to be modified. + * @param fieldName the field to modify. + * @param extraElements elements to append at the end of the array. + */ + public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) + throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + Field jlrField = findField(instance, fieldName); + + Object[] original = (Object[]) jlrField.get(instance); + Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length); + + // NOTE: changed to copy extraElements first, for patch load first + + System.arraycopy(extraElements, 0, combined, 0, extraElements.length); + System.arraycopy(original, 0, combined, extraElements.length, original.length); + + jlrField.set(instance, combined); + } + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareResPatchInfo.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareResPatchInfo.java new file mode 100644 index 00000000..2aff9593 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareResPatchInfo.java @@ -0,0 +1,189 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import com.tencent.tinker.loader.TinkerRuntimeException; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.regex.Pattern; + +/** + * Created by zhangshaowen on 16/8/9. + */ +public class ShareResPatchInfo { + public String arscBaseCrc = null; + + public String resArscMd5 = null; + public ArrayList addRes = new ArrayList<>(); + public ArrayList deleteRes = new ArrayList<>(); + public ArrayList modRes = new ArrayList<>(); + //use linkHashMap instead? + public ArrayList largeModRes = new ArrayList<>(); + public HashMap largeModMap = new HashMap<>(); + + public HashSet patterns = new HashSet<>(); + + public static void parseAllResPatchInfo(String meta, ShareResPatchInfo info) { + if (meta == null || meta.length() == 0) { + return; + } + String[] lines = meta.split("\n"); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (line == null || line.length() <= 0) { + continue; + } + if (line.startsWith(ShareConstants.RES_TITLE)) { + final String[] kv = line.split(",", 3); + info.arscBaseCrc = kv[1]; + info.resArscMd5 = kv[2]; + } else if (line.startsWith(ShareConstants.RES_PATTERN_TITLE)) { + final String[] kv = line.split(":", 2); + int size = Integer.parseInt(kv[1]); + for (; size > 0; size--) { + info.patterns.add(convertToPatternString(lines[i + 1])); + i++; + } + } else if (line.startsWith(ShareConstants.RES_ADD_TITLE)) { + final String[] kv = line.split(":", 2); + int size = Integer.parseInt(kv[1]); + for (; size > 0; size--) { + info.addRes.add(lines[i + 1]); + i++; + } + } else if (line.startsWith(ShareConstants.RES_MOD_TITLE)) { + final String[] kv = line.split(":", 2); + int size = Integer.parseInt(kv[1]); + for (; size > 0; size--) { + info.modRes.add(lines[i + 1]); + i++; + } + } else if (line.startsWith(ShareConstants.RES_LARGE_MOD_TITLE)) { + final String[] kv = line.split(":", 2); + int size = Integer.parseInt(kv[1]); + for (; size > 0; size--) { + String nextLine = lines[i + 1]; + final String[] data = nextLine.split(",", 3); + String name = data[0]; + LargeModeInfo largeModeInfo = new LargeModeInfo(); + largeModeInfo.md5 = data[1]; + largeModeInfo.crc = Long.parseLong(data[2]); + info.largeModRes.add(name); + info.largeModMap.put(name, largeModeInfo); + i++; + } + } else if (line.startsWith(ShareConstants.RES_DEL_TITLE)) { + final String[] kv = line.split(":", 2); + int size = Integer.parseInt(kv[1]); + for (; size > 0; size--) { + info.deleteRes.add(lines[i + 1]); + i++; + } + } + } + + } + + public static boolean checkFileInPattern(HashSet patterns, String key) { + if (!patterns.isEmpty()) { + for (Iterator it = patterns.iterator(); it.hasNext();) { + Pattern p = it.next(); + if (p.matcher(key).matches()) { + return true; + } + } + } + return false; + } + + public static boolean checkResPatchInfo(ShareResPatchInfo info) { + if (info == null) { + return false; + } + String md5 = info.resArscMd5; + if (md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { + return false; + } + return true; + } + + private static Pattern convertToPatternString(String input) { + //convert \\. + if (input.contains(".")) { + input = input.replaceAll("\\.", "\\\\."); + } + //convert ?to . + if (input.contains("?")) { + input = input.replaceAll("\\?", "\\."); + } + //convert * to.* + if (input.contains("*")) { + input = input.replace("*", ".*"); + } + Pattern pattern = Pattern.compile(input); + return pattern; + } + + public static void parseResPatchInfoFirstLine(String meta, ShareResPatchInfo info) { + if (meta == null || meta.length() == 0) { + return; + } + String[] lines = meta.split("\n"); + String firstLine = lines[0]; + if (firstLine == null || firstLine.length() <= 0) { + throw new TinkerRuntimeException("res meta Corrupted:" + meta); + } + final String[] kv = firstLine.split(",", 3); + info.arscBaseCrc = kv[1]; + info.resArscMd5 = kv[2]; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("resArscMd5:" + resArscMd5 + "\n"); + sb.append("arscBaseCrc:" + arscBaseCrc + "\n"); + + for (Pattern pattern : patterns) { + sb.append("pattern:" + pattern + "\n"); + } + for (String add : addRes) { + sb.append("addedSet:" + add + "\n"); + } + for (String mod : modRes) { + sb.append("modifiedSet:" + mod + "\n"); + } + for (String largeMod : largeModRes) { + sb.append("largeModifiedSet:" + largeMod + "\n"); + } + for (String del : deleteRes) { + sb.append("deletedSet:" + del + "\n"); + } + return sb.toString(); + } + + public static class LargeModeInfo { + public String md5 = null; + public long crc; + public File file = null; + } + +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareSecurityCheck.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareSecurityCheck.java new file mode 100644 index 00000000..d62e4693 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareSecurityCheck.java @@ -0,0 +1,211 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import com.tencent.tinker.loader.TinkerRuntimeException; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Created by zhangshaowen on 16/3/10. + */ +public class ShareSecurityCheck { + private static final String TAG = "ShareSecurityCheck"; + /** + * static to faster + * public key + */ + private static PublicKey mPublicKey = null; + + private final Context mContext; + private final HashMap metaContentMap; + private HashMap packageProperties; + + public ShareSecurityCheck(Context context) { + mContext = context; + metaContentMap = new HashMap<>(); + + if (mPublicKey == null) { + init(mContext); + } + } + + public HashMap getMetaContentMap() { + return metaContentMap; + } + + /** + * get the base tinkerId + * + * @return + */ + public String getTinkerID() { + if (packageProperties != null) { + String tinkerId = packageProperties.get(ShareConstants.TINKER_ID); + return tinkerId; + } + return null; + } + + /** + * get the new tinkerId + * + * @return + */ + public String getNewTinkerID() { + if (packageProperties != null) { + String tinkerId = packageProperties.get(ShareConstants.NEW_TINKER_ID); + + return tinkerId; + } + return null; + } + /** + * Nullable + * + * @return HashMap + */ + public HashMap getPackagePropertiesIfPresent() { + if (packageProperties != null) { + return packageProperties; + } + + String property = metaContentMap.get(ShareConstants.PACKAGE_META_FILE); + + if (property == null) { + return null; + } + + String[] lines = property.split("\n"); + for (final String line : lines) { + if (line == null || line.length() <= 0) { + continue; + } + //it is comment + if (line.startsWith("#")) { + continue; + } + final String[] kv = line.split("=", 2); + if (kv == null || kv.length < 2) { + continue; + } + if (packageProperties == null) { + packageProperties = new HashMap<>(); + } + packageProperties.put(kv[0].trim(), kv[1].trim()); + } + return packageProperties; + } + + public boolean verifyPatchMetaSignature(File path) { + if (path == null || !path.isFile() || !path.exists() || path.length() == 0) { + return false; + } + JarFile jarFile = null; + try { + jarFile = new JarFile(path); + final Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry jarEntry = entries.nextElement(); + // no code + if (jarEntry == null) { + continue; + } + + final String name = jarEntry.getName(); + if (name.startsWith("META-INF/")) { + continue; + } + //for faster, only check the meta.txt files + //we will check other files's mad5 written in meta files + if (!name.endsWith(ShareConstants.META_SUFFIX)) { + continue; + } + metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry)); + Certificate[] certs = jarEntry.getCertificates(); + if (certs == null) { + return false; + } + if (!check(path, certs)) { + return false; + } + } + } catch (Exception e) { + throw new TinkerRuntimeException( + String.format("ShareSecurityCheck file %s, size %d verifyPatchMetaSignature fail", path.getAbsolutePath(), path.length()), e); + } finally { + try { + if (jarFile != null) { + jarFile.close(); + } + } catch (IOException e) { + Log.e(TAG, path.getAbsolutePath(), e); + } + } + return true; + } + + + // verify the signature of the Apk + private boolean check(File path, Certificate[] certs) { + if (certs.length > 0) { + for (int i = certs.length - 1; i >= 0; i--) { + try { + certs[i].verify(mPublicKey); + return true; + } catch (Exception e) { + Log.e(TAG, path.getAbsolutePath(), e); + } + } + } + return false; + } + + @SuppressLint("PackageManagerGetSignatures") + private void init(Context context) { + ByteArrayInputStream stream = null; + try { + PackageManager pm = context.getPackageManager(); + String packageName = context.getPackageName(); + PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + stream = new ByteArrayInputStream(packageInfo.signatures[0].toByteArray()); + X509Certificate cert = (X509Certificate) certFactory.generateCertificate(stream); + mPublicKey = cert.getPublicKey(); + } catch (Exception e) { + throw new TinkerRuntimeException("ShareSecurityCheck init public key fail", e); + } finally { + SharePatchFileUtil.closeQuietly(stream); + } + } +} diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerInternals.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerInternals.java new file mode 100644 index 00000000..404bbdcd --- /dev/null +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerInternals.java @@ -0,0 +1,328 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader.shareutil; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Created by zhangshaowen on 16/3/10. + */ +public class ShareTinkerInternals { + private static final String TAG = "Tinker.TinkerInternals"; + private static final boolean VM_IS_ART = isVmArt(System.getProperty("java.vm.version")); + /** + * or you may just hardcode them in your app + */ + private static String processName = null; + private static String tinkerID = null; + + public static boolean isVmArt() { + return VM_IS_ART; + } + + public static boolean isNullOrNil(final String object) { + if ((object == null) || (object.length() <= 0)) { + return true; + } + return false; + } + + /** + * check patch file signature and TINKER_ID + * + * @param context + * @param patchFile + * @param securityCheck + * @return + */ + public static int checkSignatureAndTinkerID(Context context, File patchFile, ShareSecurityCheck securityCheck) { + if (!securityCheck.verifyPatchMetaSignature(patchFile)) { + return ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL; + } + + String oldTinkerId = getManifestTinkerID(context); + if (oldTinkerId == null) { + return ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND; + } + + HashMap properties = securityCheck.getPackagePropertiesIfPresent(); + + if (properties == null) { + return ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND; + } + + String patchTinkerId = properties.get(ShareConstants.TINKER_ID); + if (patchTinkerId == null) { + return ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND; + } + if (!oldTinkerId.equals(patchTinkerId)) { + return ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL; + } + return ShareConstants.ERROR_PACKAGE_CHECK_OK; + } + + /** + * not like {@cod ShareSecurityCheck.getPackagePropertiesIfPresent} + * we don't check Signatures or other files, we just get the package meta's properties directly + * @param patchFile + * @return + */ + public static Properties fastGetPatchPackageMeta(File patchFile) { + if (patchFile == null || !patchFile.isFile() || patchFile.length() == 0) { + Log.e(TAG, "patchFile is illegal"); + return null; + } + ZipFile zipFile = null; + try { + zipFile = new ZipFile(patchFile); + ZipEntry packageEntry = zipFile.getEntry(ShareConstants.PACKAGE_META_FILE); + if (packageEntry == null) { + Log.e(TAG, "patch meta entry not found"); + return null; + } + InputStream inputStream = null; + try { + inputStream = zipFile.getInputStream(packageEntry); + Properties properties = new Properties(); + properties.load(inputStream); + return properties; + } finally { + SharePatchFileUtil.closeQuietly(inputStream); + } + } catch (IOException e) { + Log.e(TAG, "fastGetPatchPackageMeta exception:" + e.getMessage()); + return null; + } finally { + SharePatchFileUtil.closeZip(zipFile); + } + } + public static String getManifestTinkerID(Context context) { + if (tinkerID != null) { + return tinkerID; + } + try { + ApplicationInfo appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + + Object object = appInfo.metaData.get(ShareConstants.TINKER_ID); + if (object != null) { + tinkerID = String.valueOf(object); + } else { + tinkerID = null; + } + } catch (Exception e) { + Log.e(TAG, "getManifestTinkerID exception:" + e.getMessage()); + return null; + } + return tinkerID; + } + + public static boolean isTinkerEnabledForDex(int flag) { + return (flag & ShareConstants.TINKER_DEX_MASK) != 0; + } + + public static boolean isTinkerEnabledForNativeLib(int flag) { + return (flag & ShareConstants.TINKER_NATIVE_LIBRARY_MASK) != 0; + } + + public static boolean isTinkerEnabledForResource(int flag) { + //FIXME:res flag depends dex flag + return (flag & ShareConstants.TINKER_RESOURCE_MASK) != 0; + } + + public static String getTypeString(int type) { + switch (type) { + case ShareConstants.TYPE_DEX: + return "dex"; + case ShareConstants.TYPE_DEX_FOR_ART: + return "dex_art"; + case ShareConstants.TYPE_DEX_OPT: + return "dex_opt"; + case ShareConstants.TYPE_LIBRARY: + return "lib"; + case ShareConstants.TYPE_PATCH_FILE: + return "patch_file"; + case ShareConstants.TYPE_PATCH_INFO: + return "patch_info"; + case ShareConstants.TYPE_RESOURCE: + return "resource"; + default: + return "unknown"; + } + } + + /** + * you can set Tinker disable in runtime at some times! + * @param context + */ + public static void setTinkerDisableWithSharedPreferences(Context context) { + SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); + sp.edit().putBoolean(ShareConstants.TINKER_ENABLE_CONFIG, false).commit(); + } + + /** + * can't load or receive any patch! + * @param context + * @return + */ + public static boolean isTinkerEnableWithSharedPreferences(Context context) { + SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); + return sp.getBoolean(ShareConstants.TINKER_ENABLE_CONFIG, true); + } + + public static boolean isTinkerEnabled(int flag) { + return (flag != ShareConstants.TINKER_DISABLE); + } + + public static boolean isTinkerEnabledAll(int flag) { + return (flag == ShareConstants.TINKER_ENABLE_ALL); + } + + public static boolean isInMainProcess(Context context) { + String pkgName = context.getPackageName(); + String processName = getProcessName(context); + if (processName == null || processName.length() == 0) { + processName = ""; + } + + return pkgName.equals(processName); + } + + public static void killAllOtherProcess(Context context) { + final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M + // BUT THAT'S ENOUGH HERE + for (ActivityManager.RunningAppProcessInfo ai : am.getRunningAppProcesses()) { + // KILL OTHER PROCESS OF MINE + if (ai.uid == android.os.Process.myUid() && ai.pid != android.os.Process.myPid()) { + android.os.Process.killProcess(ai.pid); + } + } + + } + + /** + * add process name cache + * + * @param context + * @return + */ + public static String getProcessName(final Context context) { + if (processName != null) { + return processName; + } + //will not null + processName = getProcessNameInternal(context); + return processName; + } + + + private static String getProcessNameInternal(final Context context) { + int myPid = android.os.Process.myPid(); + + if (context == null || myPid <= 0) { + return ""; + } + + ActivityManager.RunningAppProcessInfo myProcess = null; + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + try { + for (ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses()) { + if (process.pid == myPid) { + myProcess = process; + break; + } + } + } catch (Exception e) { + Log.e(TAG, "getProcessNameInternal exception:" + e.getMessage()); + } + + if (myProcess != null) { + return myProcess.processName; + } + + byte[] b = new byte[128]; + FileInputStream in = null; + try { + in = new FileInputStream("/proc/" + myPid + "/cmdline"); + int len = in.read(b); + if (len > 0) { + for (int i = 0; i < len; i++) { // lots of '0' in tail , remove them + if (b[i] > 128 || b[i] <= 0) { + len = i; + break; + } + } + return new String(b, 0, len); + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception e) { + } + } + + return ""; + } + + /** + * vm whether it is art + * @return + */ + private static boolean isVmArt(String versionString) { + boolean isArt = false; + if (versionString != null) { + Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); + if (matcher.matches()) { + try { + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + isArt = (major > 2) + || ((major == 2) + && (minor >= 1)); + } catch (NumberFormatException e) { + // let isMultidexCapable be false + } + } + } + return isArt; + } +} diff --git a/tinker-android/tinker-android-loader/src/test/java/com/tencent/tinker/loader/ExampleUnitTest.java b/tinker-android/tinker-android-loader/src/test/java/com/tencent/tinker/loader/ExampleUnitTest.java new file mode 100644 index 00000000..8074a327 --- /dev/null +++ b/tinker-android/tinker-android-loader/src/test/java/com/tencent/tinker/loader/ExampleUnitTest.java @@ -0,0 +1,31 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.loader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/tinker-build/.gitignore b/tinker-build/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tinker-build/tinker-patch-cli/.gitignore b/tinker-build/tinker-patch-cli/.gitignore new file mode 100644 index 00000000..5f1b6b07 --- /dev/null +++ b/tinker-build/tinker-patch-cli/.gitignore @@ -0,0 +1,3 @@ +/build +/tool_output/*.apk +/tool_output/TinkerPatch \ No newline at end of file diff --git a/tinker-build/tinker-patch-cli/build.gradle b/tinker-build/tinker-patch-cli/build.gradle new file mode 100644 index 00000000..9b48cc3a --- /dev/null +++ b/tinker-build/tinker-patch-cli/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'java' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':tinker-build:tinker-patch-lib') +} + +jar { + manifest { + attributes 'Main-Class': 'com.tencent.tinker.patch.CliMain' + attributes 'Manifest-Version': version + } + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +// copy the jar to work directory +task buildSdk(type: Copy, dependsOn: [build, jar]) { + from('build/libs') { + include '*.jar' + exclude '*-javadoc.jar' + exclude '*-sources.jar' + } + from('./tool_output') { + include '*.*' + } + into(rootProject.file("buildSdk/build")) +} + +defaultTasks 'buildSdk' diff --git a/tinker-build/tinker-patch-cli/src/main/java/com/tencent/tinker/patch/CliMain.java b/tinker-build/tinker-patch-cli/src/main/java/com/tencent/tinker/patch/CliMain.java new file mode 100644 index 00000000..37dbf0b1 --- /dev/null +++ b/tinker-build/tinker-patch-cli/src/main/java/com/tencent/tinker/patch/CliMain.java @@ -0,0 +1,215 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.patch; + + +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.patch.Runner; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; + +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +import javax.xml.parsers.ParserConfigurationException; + +/** + * Created by zhangshaowen on 2/27/16. + * do not use Logger here + */ +public class CliMain extends Runner { + private static final String ARG_HELP = "--help"; + private static final String ARG_OUT = "-out"; + private static final String ARG_CONFIG = "-config"; + private static final String ARG_OLD = "-old"; + private static final String ARG_NEW = "-new"; + + + protected static String mRunningLocation; + + public static void main(String[] args) { + mBeginTime = System.currentTimeMillis(); + CliMain m = new CliMain(); + setRunningLocation(m); + m.run(args); + } + + private static void setRunningLocation(CliMain m) { + mRunningLocation = m.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); + try { + mRunningLocation = URLDecoder.decode(mRunningLocation, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + if (mRunningLocation.endsWith(".jar")) { + mRunningLocation = mRunningLocation.substring(0, mRunningLocation.lastIndexOf(File.separator) + 1); + } + File f = new File(mRunningLocation); + mRunningLocation = f.getAbsolutePath(); + } + + private static void printUsage(PrintStream out) { + // TODO: Look up launcher script name! + String command = "tinker.jar"; //$NON-NLS-1$ + out.println(); + out.println(); + out.println("Usage: java -jar " + command + " " + ARG_OLD + " old.apk " + ARG_NEW + " new.apk " + ARG_CONFIG + " tinker_config.xml " + ARG_OUT + " output_path"); + out.println("others please contact us"); + } + + private void run(String[] args) { + if (args.length < 1) { + goToError(); + } + try { + + ReadArgs readArgs = new ReadArgs(args).invoke(); + File configFile = readArgs.getConfigFile(); + File outputFile = readArgs.getOutputFile(); + File oldApkFile = readArgs.getOldApkFile(); + File newApkFile = readArgs.getNewApkFile(); + + if (oldApkFile == null || newApkFile == null) { + Logger.e("Missing old apk or new apk file argument"); + goToError(); + } + + if (outputFile == null) { + outputFile = new File(mRunningLocation, TypedValue.PATH_DEFAULT_OUTPUT); + } + + loadConfigFromXml(configFile, outputFile, oldApkFile, newApkFile); + Logger.initLogger(config); + tinkerPatch(); + } catch (IOException e) { + e.printStackTrace(); + goToError(); + } finally { + Logger.closeLogger(); + } + } + + private void loadConfigFromXml(File configFile, File outputFile, File oldApkFile, File newApkFile) { + if (configFile == null) { + configFile = new File(mRunningLocation + File.separator + TypedValue.FILE_CONFIG); + if (!configFile.exists()) { + System.err.printf("the config file %s does not exit\n", configFile.getAbsolutePath()); + printUsage(System.err); + System.exit(ERRNO_USAGE); + } + } + try { + config = new Configuration(configFile, outputFile, oldApkFile, newApkFile); + + } catch (IOException | ParserConfigurationException | SAXException e) { + e.printStackTrace(); + goToError(); + } catch (TinkerPatchException e) { + e.printStackTrace(); + goToError(); + } + } + + public void goToError() { + printUsage(System.err); + System.exit(ERRNO_USAGE); + } + + private class ReadArgs { + private String[] args; + private File configFile; + private File outputFile; + private File oldApkFile; + private File newApkFile; + + ReadArgs(String[] args) { + this.args = args; + } + + public File getConfigFile() { + return configFile; + } + + public File getOutputFile() { + return outputFile; + } + + public File getOldApkFile() { + return oldApkFile; + } + + public File getNewApkFile() { + return newApkFile; + } + + public ReadArgs invoke() { + for (int index = 0; index < args.length; index++) { + String arg = args[index]; + if (arg.equals(ARG_HELP) || arg.equals("-h")) { + goToError(); + } else if (arg.equals(ARG_CONFIG)) { + if (index == args.length - 1 || !args[index + 1].endsWith(TypedValue.FILE_XML)) { + System.err.println("Missing XML configuration file argument"); + goToError(); + } + configFile = new File(args[++index]); + if (!configFile.exists()) { + System.err.println(configFile.getAbsolutePath() + " does not exist"); + goToError(); + } + + System.out.println("special configFile file path:" + configFile.getAbsolutePath()); + + } else if (arg.equals(ARG_OUT)) { + if (index == args.length - 1) { + System.err.println("Missing output file argument"); + goToError(); + } + outputFile = new File(args[++index]); + File parent = outputFile.getParentFile(); + if (parent != null && (!parent.exists())) { + parent.mkdirs(); + } + System.out.printf("special output directory path: %s\n", outputFile.getAbsolutePath()); + + } else if (arg.equals(ARG_OLD)) { + if (index == args.length - 1) { + System.err.println("Missing old apk file argument"); + goToError(); + } + oldApkFile = new File(args[++index]); + } else if (arg.equals(ARG_NEW)) { + if (index == args.length - 1) { + System.err.println("Missing new apk file argument"); + goToError(); + } + newApkFile = new File(args[++index]); + } + } + return this; + } + } + + +} + diff --git a/tinker-build/tinker-patch-cli/tool_output/release.keystore b/tinker-build/tinker-patch-cli/tool_output/release.keystore new file mode 100644 index 00000000..c32e009e Binary files /dev/null and b/tinker-build/tinker-patch-cli/tool_output/release.keystore differ diff --git a/tinker-build/tinker-patch-cli/tool_output/tinker_config.xml b/tinker-build/tinker-patch-cli/tool_output/tinker_config.xml new file mode 100644 index 00000000..f62489f0 --- /dev/null +++ b/tinker-build/tinker-patch-cli/tool_output/tinker_config.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tinker-build/tinker-patch-cli/tool_output/tinker_multidexkeep.pro b/tinker-build/tinker-patch-cli/tool_output/tinker_multidexkeep.pro new file mode 100644 index 00000000..af70fdb0 --- /dev/null +++ b/tinker-build/tinker-patch-cli/tool_output/tinker_multidexkeep.pro @@ -0,0 +1,21 @@ +-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle { + *; +} + +-keep public class * extends com.tencent.tinker.loader.TinkerLoader { + *; +} + +-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication { + *; +} + +#your dex.loader pattern here +-keep class com.tencent.tinker.loader.** { + *; +} + +-keep class tinker.sample.android.app.SampleApplication { + *; +} + diff --git a/tinker-build/tinker-patch-cli/tool_output/tinker_proguard.pro b/tinker-build/tinker-patch-cli/tool_output/tinker_proguard.pro new file mode 100644 index 00000000..23c74455 --- /dev/null +++ b/tinker-build/tinker-patch-cli/tool_output/tinker_proguard.pro @@ -0,0 +1,31 @@ +#-applymapping "old apk mapping here" + +-keepattributes *Annotation* +-dontwarn com.tencent.tinker.anno.AnnotationProcessor +-keep @com.tencent.tinker.anno.DefaultLifeCycle public class * +-keep public class * extends android.app.Application { + *; +} + +-keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle { + *; +} +-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle { + *; +} + +-keep public class com.tencent.tinker.loader.TinkerLoader { + *; +} +-keep public class * extends com.tencent.tinker.loader.TinkerLoader { + *; +} + +-keep public class com.tencent.tinker.loader.TinkerTestDexLoad { + *; +} + +#your dex.loader pattern here +-keep class com.tencent.tinker.loader.* +-keep class tinker.sample.android.app.SampleApplication + diff --git a/tinker-build/tinker-patch-gradle-plugin/.gitignore b/tinker-build/tinker-patch-gradle-plugin/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-build/tinker-patch-gradle-plugin/build.gradle b/tinker-build/tinker-patch-gradle-plugin/build.gradle new file mode 100644 index 00000000..d07a0497 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'groovy' + + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +dependencies { + compile gradleApi() + compile localGroovy() +// compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':tinker-build:tinker-patch-lib') + compile 'com.google.gradle:osdetector-gradle-plugin:1.2.1' +} + +repositories { + mavenCentral() +} + +sourceSets { + main { + groovy { + srcDir 'src/main/groovy' + } + + resources { + srcDir 'src/main/resources' + } + } +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') + diff --git a/tinker-build/tinker-patch-gradle-plugin/gradle.properties b/tinker-build/tinker-patch-gradle-plugin/gradle.properties new file mode 100644 index 00000000..961b8b4f --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=tinker-patch-gradle-plugin +POM_NAME=Tinker Patch Gradle Plugin +POM_PACKAGING=jar \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/TinkerPatchPlugin.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/TinkerPatchPlugin.groovy new file mode 100644 index 00000000..fe44cea2 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/TinkerPatchPlugin.groovy @@ -0,0 +1,158 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle + +import com.tencent.tinker.build.gradle.extension.TinkerBuildConfigExtension +import com.tencent.tinker.build.gradle.extension.TinkerDexExtension +import com.tencent.tinker.build.gradle.extension.TinkerLibExtension +import com.tencent.tinker.build.gradle.extension.TinkerPackageConfigExtension +import com.tencent.tinker.build.gradle.extension.TinkerPatchExtension +import com.tencent.tinker.build.gradle.extension.TinkerResourceExtension +import com.tencent.tinker.build.gradle.extension.TinkerSevenZipExtension +import com.tencent.tinker.build.gradle.task.TinkerManifestTask +import com.tencent.tinker.build.gradle.task.TinkerMultidexConfigTask +import com.tencent.tinker.build.gradle.task.TinkerPatchSchemaTask +import com.tencent.tinker.build.gradle.task.TinkerProguardConfigTask +import com.tencent.tinker.build.gradle.task.TinkerResourceIdTask +import com.tencent.tinker.build.util.FileOperation +import com.tencent.tinker.build.util.TypedValue +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Registers the plugin's tasks. + * + * @author zhangshaowen + */ + +class TinkerPatchPlugin implements Plugin { + public static final String TINKER_INTERMEDIATES = "build/intermediates/tinker_intermediates/" + + @Override + public void apply(Project project) { + project.apply plugin: 'osdetector' + + project.extensions.create('tinkerPatch', TinkerPatchExtension) + + project.tinkerPatch.extensions.create('buildConfig', TinkerBuildConfigExtension, project) + + project.tinkerPatch.extensions.create('dex', TinkerDexExtension, project) + project.tinkerPatch.extensions.create('lib', TinkerLibExtension) + project.tinkerPatch.extensions.create('res', TinkerResourceExtension) + project.tinkerPatch.extensions.create('packageConfig', TinkerPackageConfigExtension, project) + project.tinkerPatch.extensions.create('sevenZip', TinkerSevenZipExtension, project) + + def configuration = project.tinkerPatch + + project.afterEvaluate { + if (!project.plugins.hasPlugin('com.android.application')) { + throw new GradleException('generateTinkerApk: Android Application plugin required') + } + + def android = project.extensions.android + //add the tinker anno resource to the package exclude option + android.packagingOptions.exclude("META-INF/services/javax.annotation.processing.Processor") + android.packagingOptions.exclude("TinkerAnnoApplication.tmpl") + //open jumboMode + android.dexOptions.jumboMode = true + + project.logger.error("----------------------tinker build warning ------------------------------------") + project.logger.error("tinker will change your build configs:") + project.logger.error("we will add TINDER_ID=${configuration.buildConfig.tinkerId} in your build output manifest file build/intermediates/manifests/full/*") + project.logger.error("") + project.logger.error("if minifyEnabled is true") + + String tempMappingPath = configuration.buildConfig.applyMapping + + if (FileOperation.isLegalFile(tempMappingPath)) { + project.logger.error("we will build ${project.getName()} apk with apply mapping file ${tempMappingPath}") + } + + project.logger.error("you will find the gen proguard rule file at ${TinkerProguardConfigTask.PROGUARD_CONFIG_PATH}") + project.logger.error("and we will help you to put it in the proguardFiles.") + project.logger.error("") + project.logger.error("if multiDexEnabled is true") + project.logger.error("you will find the gen multiDexKeepProguard file at ${TinkerMultidexConfigTask.MULTIDEX_CONFIG_PATH}") + project.logger.error("and you should copy it to your own multiDex keep proguard file yourself.") + project.logger.error("") + project.logger.error("if applyResourceMapping file is exist") + String tempResourceMappingPath = configuration.buildConfig.applyResourceMapping + if (FileOperation.isLegalFile(tempResourceMappingPath)) { + project.logger.error("we will build ${project.getName()} apk with resource R.txt ${tempResourceMappingPath} file") + } else { + project.logger.error("we will build ${project.getName()} apk with resource R.txt file") + } + project.logger.error("if resources.arsc has changed, you should use applyResource mode to build the new apk!") + project.logger.error("-----------------------------------------------------------------") + + android.applicationVariants.all { variant -> + + def variantOutput = variant.outputs.first() + def variantName = variant.name.capitalize() + + TinkerPatchSchemaTask tinkerPatchBuildTask = project.tasks.create("tinkerPatch${variantName}", TinkerPatchSchemaTask) + tinkerPatchBuildTask.dependsOn variant.assemble + + tinkerPatchBuildTask.signconfig = variant.apkVariantData.variantConfiguration.signingConfig + + variant.outputs.each { output -> + tinkerPatchBuildTask.buildApkPath = output.outputFile + File parentFile = output.outputFile + tinkerPatchBuildTask.outputFolder = "${parentFile.getParentFile().getParentFile().getAbsolutePath()}/" + TypedValue.PATH_DEFAULT_OUTPUT + "/" + variant.dirName + } + + // Create a task to add a build TINKER_ID to AndroidManifest.xml + // This task must be called after "process${variantName}Manifest", since it + // requires that an AndroidManifest.xml exists in `build/intermediates`. + TinkerManifestTask manifestTask = project.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask) + manifestTask.manifestPath = variantOutput.processManifest.manifestOutputFile + manifestTask.mustRunAfter variantOutput.processManifest + + variantOutput.processResources.dependsOn manifestTask + + // Add this proguard settings file to the list + boolean proguardEnable = variant.getBuildType().buildType.minifyEnabled + + if (proguardEnable) { + TinkerProguardConfigTask proguardConfigTask = project.tasks.create("tinkerProcess${variantName}Proguard", TinkerProguardConfigTask) + proguardConfigTask.applicationVariant = variant + variantOutput.packageApplication.dependsOn proguardConfigTask + } + + // Add this multidex proguard settings file to the list + boolean multiDexEnabled = variant.apkVariantData.variantConfiguration.isMultiDexEnabled() + + if (multiDexEnabled) { + TinkerMultidexConfigTask multidexConfigTask = project.tasks.create("tinkerProcess${variantName}MultidexKeep", TinkerMultidexConfigTask) + multidexConfigTask.applicationVariant = variant + variantOutput.packageApplication.dependsOn multidexConfigTask + } +// if (tempResourceFile != null && tempResourceFile.exists() && tempResourceFile.isFile()) { + TinkerResourceIdTask applyResourceTask = project.tasks.create("tinkerProcess${variantName}ResourceId", TinkerResourceIdTask) + applyResourceTask.resDir = variantOutput.processResources.resDir + variantOutput.processResources.dependsOn applyResourceTask +// } +// else { +// project.logger.error("apply resource mapping file ${resourceMappingFile} is not exist, just ignore") +// } + } + } + + } + +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerBuildConfigExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerBuildConfigExtension.groovy new file mode 100644 index 00000000..4ad1b62e --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerBuildConfigExtension.groovy @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import org.gradle.api.GradleException +import org.gradle.api.Project + +/** + * The configuration properties. + * + * @author zhangshaowen + */ + +public class TinkerBuildConfigExtension { + + /** + * Specifies the old apk's mapping file for proguard to applymapping + */ + String applyMapping + + /** + * Specifies the old resource id mapping(R.txt) file to applyResourceMapping + */ + String applyResourceMapping + + /** + * because we don't want to check the base apk with md5 in the runtime(it is slow) + * tinkerId is use to identify the unique base apk when the patch is tried to apply. + * we can use git rev, svn rev or simply versionCode. + * we will gen the tinkerId in your manifest automatic + */ + String tinkerId + + private Project project + + boolean usingResourceMapping + + public TinkerBuildConfigExtension(Project project) { + this.project = project + applyMapping = "" + applyResourceMapping = "" + tinkerId = null + usingResourceMapping = false + } + + void checkParameter() { + if (tinkerId == null || tinkerId.isEmpty()) { + throw new GradleException("you must set your tinkerId to identify the base apk!") + } + } + + + @Override + public String toString() { + """| applyMapping = ${applyMapping} + | applyResourceMapping = ${applyResourceMapping} + | tinkerId = ${tinkerId} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerDexExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerDexExtension.groovy new file mode 100644 index 00000000..2add6dc0 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerDexExtension.groovy @@ -0,0 +1,67 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import org.gradle.api.GradleException +import org.gradle.api.Project + +/** + * The configuration properties. + * + * @author zhangshaowen + */ + +public class TinkerDexExtension { + /** + * raw or jar, if you want to support below 4.0, you should use jar + * default: raw, keep the orginal file type + */ + String dexMode; + /** + * the dex file patterns, which dex or jar files will be deal to gen patch + * such as [classes.dex, classes-*.dex, assets/multiDex/*.jar] + */ + Iterable pattern; + /** + * the loader files, they will be removed during gen patch main dex + * and they should be at the primary dex + * such as [com.tencent.tinker.loader.*, com.tinker.sample.MyApplication] + */ + Iterable loader; + private Project project; + + public TinkerDexExtension(Project project) { + dexMode = "jar" + pattern = [] + loader = [] + this.project = project + } + + void checkDexMode() { + if (!dexMode.equals("raw") && !dexMode.equals("jar")) { + throw new GradleException("dexMode can be only one of 'jar' or 'raw'!") + } + } + + @Override + public String toString() { + """| dexMode = ${dexMode} + | pattern = ${pattern} + | loader = ${loader} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerLibExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerLibExtension.groovy new file mode 100644 index 00000000..eff1410f --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerLibExtension.groovy @@ -0,0 +1,41 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension +/** + * The configuration properties. + * + * @author zhangshaowen + */ + +public class TinkerLibExtension { + /** + * the library file patterns, which files will be deal to gen patch + * such as [lib/armeabi/*.so, assets/libs/*.so] + */ + Iterable pattern; + + + public TinkerLibExtension() { + pattern = [] + } + + @Override + public String toString() { + """| pattern = ${pattern} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPackageConfigExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPackageConfigExtension.groovy new file mode 100644 index 00000000..fbf9221a --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPackageConfigExtension.groovy @@ -0,0 +1,93 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import com.tencent.tinker.build.apkparser.AndroidManifest +import org.gradle.api.GradleException +import org.gradle.api.Project + +/** + * The configuration properties. + * + * @author zhangshaowen + */ + +public class TinkerPackageConfigExtension { + /** + * we can gen package config file while configField method + */ + private Map fields + private Project project; + private AndroidManifest androidManifest; + + + public TinkerPackageConfigExtension(project) { + fields = [:] + this.project = project + } + + void configField(String name, String value) { + fields.put(name, value) + } + + Map getFields() { + return fields + } + + private void createApkMetaFile() { + if (androidManifest == null) { + File oldPakFile = new File(project.tinkerPatch.oldApk) + + if (!oldPakFile.exists()) { + throw new GradleException( + String.format("old apk file %s is not exist, you can set the value directly!", oldPakFile) + ) + } + androidManifest = AndroidManifest.getAndroidManifest(oldPakFile); + } + } + + String getVersionCodeFromOldAPk() { + createApkMetaFile() + return androidManifest.apkMeta.versionCode; + } + + String getVersionNameFromOldAPk() { + createApkMetaFile() + return androidManifest.apkMeta.versionName; + } + + String getMinSdkVersionFromOldAPk() { + createApkMetaFile() + return androidManifest.apkMeta.minSdkVersion; + } + + String getMetaDataFromOldApk(String name) { + createApkMetaFile() + String value = androidManifest.metaDatas.get(name); + if (value == null) { + throw new GradleException("can't find meta data " + name + " from the old apk manifest file!") + } + return value + } + + @Override + public String toString() { + """| fields = ${fields} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPatchExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPatchExtension.groovy new file mode 100644 index 00000000..81210332 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPatchExtension.groovy @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import org.gradle.api.GradleException; + +/** + * The configuration properties. + * + * @author zhangshaowen + */ + +public class TinkerPatchExtension { + /** + * Specifies the old apk path to diff with the new apk + */ + String oldApk + + /** + * if there is loader class changes, + * or Activity, Service, Receiver, Provider change, it will terminal + * if ignoreWarning is false + * default: false + */ + boolean ignoreWarning + + /** + * if sign the patch file with the android signConfig + * default: true + */ + boolean useSign + + public TinkerPatchExtension() { + oldApk = "" + ignoreWarning = false + useSign = true + } + + void checkParameter() { + if (oldApk == null) { + throw new GradleException("old apk is null, you must set the correct old apk value!") + } else if (!new File(oldApk).exists()) { + throw new GradleException("old apk ${oldApk} is not exist, you must set the correct old apk value!") + } + } + + @Override + public String toString() { + """| oldApk = ${oldApk} + | ignoreWarning = ${ignoreWarning} + | useSign = ${useSign} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerResourceExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerResourceExtension.groovy new file mode 100644 index 00000000..bdff32c6 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerResourceExtension.groovy @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import org.gradle.api.GradleException + +/** + * The configuration properties. + * + * @author liangwenxiang + */ + +public class TinkerResourceExtension { + /** + * the resource file patterns, which files will be deal to gen patch + * such as [res/*, assets/*, resources.arsc] + */ + Iterable pattern + /** + * the resource file ignoreChange patterns, ignore add, delete or modify resource change + * Warning, we can only use for files no relative with resources.arsc + */ + Iterable ignoreChange + + /** + * default 100kb + * for modify resource, if it is larger than 'largeModSize' + * we would like to use bsdiff algorithm to reduce patch file size + */ + int largeModSize + + public TinkerResourceExtension() { + pattern = [] + ignoreChange = [] + largeModSize = 100 + } + void checkParameter() { + if (largeModSize <= 0) { + throw new GradleException("largeModSize must be larger than 0") + } + } + + @Override + public String toString() { + """| pattern = ${pattern} + | exclude = ${ignoreChange} + | largeModSize = ${largeModSize}kb + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerSevenZipExtension.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerSevenZipExtension.groovy new file mode 100644 index 00000000..f297a363 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerSevenZipExtension.groovy @@ -0,0 +1,90 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.extension + +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency + +/** + * The configuration properties. + * you should only set one of them, if path is Specified, it will overwrite the artifact param + * @author zhangshaowen + */ +public class TinkerSevenZipExtension { + /** + * Specifies an artifact spec for downloading the executable from + * repositories. spec format: '::' + */ + String zipArtifact + /** + * Specifies a local path. + * if path is Specified, it will overwrite the artifact param + * such as/usr/local/bin/7za + * if you do not set the zipArtifact and path, We will try to use 7za directly + */ + String path + + private Project project; + + public TinkerSevenZipExtension(Project project) { + zipArtifact = null + path = null + this.project = project + } + + void resolveZipFinalPath() { + if (path != null) + return + + if (this.zipArtifact != null) { + def groupId, finalArtifact, version + Configuration config = project.configurations.create("sevenZipToolsLocator") { + visible = false + transitive = false + extendsFrom = [] + } + + (groupId, finalArtifact, version) = this.zipArtifact.split(":") + def notation = [group : groupId, + name : finalArtifact, + version : version, + classifier: project.osdetector.classifier, + ext : 'exe'] +// println "Resolving artifact: ${notation}" + Dependency dep = project.dependencies.add(config.name, notation) + File file = config.fileCollection(dep).singleFile + if (!file.canExecute() && !file.setExecutable(true)) { + throw new GradleException("Cannot set ${file} as executable") + } +// println "Resolved artifact: ${file}" + this.path = file.path + } + //use system 7za + if (this.path == null) { + this.path = "7za" + } + } + + @Override + public String toString() { + """| zipArtifact = ${zipArtifact} + | path = ${path} + """.stripMargin() + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerManifestTask.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerManifestTask.groovy new file mode 100644 index 00000000..0b60c831 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerManifestTask.groovy @@ -0,0 +1,79 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.task + +import com.tencent.tinker.build.gradle.TinkerPatchPlugin +import com.tencent.tinker.build.util.FileOperation +import groovy.xml.Namespace +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.TaskAction + +/** + * The configuration properties. + * + * @author zhangshaowen + */ +public class TinkerManifestTask extends DefaultTask { + static final String MANIFEST_XML = TinkerPatchPlugin.TINKER_INTERMEDIATES + "AndroidManifest.xml" + static final String TINKER_ID = "TINKER_ID" + String manifestPath + TinkerManifestTask() { + group = 'tinker' + } + + @TaskAction + def updateManifest() { + // Parse the AndroidManifest.xml + String tinkerValue = project.extensions.tinkerPatch.buildConfig.tinkerId + if (tinkerValue == null || tinkerValue.isEmpty()) { + throw new GradleException('tinkerId is not set!!!') + } + project.logger.error("tinker add ${tinkerValue} to your AndroidManifest.xml ${manifestPath}") + + def ns = new Namespace("http://schemas.android.com/apk/res/android", "android") + def xml = new XmlParser().parse(manifestPath) + + def application = xml.application[0] + if (application) { + def metaDataTags = application['meta-data'] + + // remove any old TINKER_ID elements + def tinkerId = metaDataTags.findAll { + it.attributes()[ns.name].equals(TINKER_ID) + }.each { + it.parent().remove(it) + } + + // Add the new TINKER_ID element + application.appendNode('meta-data', [(ns.name): TINKER_ID, (ns.value): tinkerValue]) + + // Write the manifest file + def writer = new FileWriter(manifestPath) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.preserveWhitespace = true + printer.print(xml) + } + File manifestFile = new File(manifestPath) + if (manifestFile.exists()) { + FileOperation.copyFileUsingStream(manifestFile, project.file(MANIFEST_XML)) + project.logger.error("tinker gen AndroidManifest.xml in ${MANIFEST_XML}") + } + + } +} + diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerMultidexConfigTask.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerMultidexConfigTask.groovy new file mode 100644 index 00000000..a7384fa7 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerMultidexConfigTask.groovy @@ -0,0 +1,82 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.task + +import com.tencent.tinker.build.gradle.TinkerPatchPlugin +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + + +/** + * The configuration properties. + * + * @author zhangshaowen + */ +public class TinkerMultidexConfigTask extends DefaultTask { + static final String MULTIDEX_CONFIG_PATH = TinkerPatchPlugin.TINKER_INTERMEDIATES + "tinker_multidexkeep.pro" + static final String MULTIDEX_CONFIG_SETTINGS = + "-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + + " *;\n" + + "}\n" + + "\n" + + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + + " *;\n" + + "}\n" + + "\n" + + "-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication {\n" + + " *;\n" + + "}" + + + def applicationVariant + + public TinkerMultidexConfigTask() { + group = 'tinker' + } + + @TaskAction + def updateTinkerProguardConfig() { + def file = project.file(MULTIDEX_CONFIG_PATH) + project.logger.error("try update tinker multidex keep proguard file with ${file}") + + // Create the directory if it doesn't exist already + file.getParentFile().mkdirs() + + // Write our recommended proguard settings to this file + FileWriter fr = new FileWriter(file.path) + + fr.write(MULTIDEX_CONFIG_SETTINGS) + fr.write("\n") + //unlike proguard, if loader endwith *, we must change to ** + fr.write("#your dex.loader patterns here\n") + Iterable loader = project.extensions.tinkerPatch.dex.loader + for (String pattern : loader) { + if (pattern.endsWith("*")) { + if (!pattern.endsWith("**")) { + pattern += "*" + } + } + fr.write("-keep class " + pattern + " {\n" + + " *;\n" + + "}\n") + fr.write("\n") + } + fr.close() + } + + +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerPatchSchemaTask.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerPatchSchemaTask.groovy new file mode 100644 index 00000000..4b7cc559 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerPatchSchemaTask.groovy @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.task +import com.tencent.tinker.build.patch.InputParam +import com.tencent.tinker.build.patch.Runner +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.TaskAction +/** + * The configuration properties. + * + * @author zhangshaowen + */ +public class TinkerPatchSchemaTask extends DefaultTask { + def configuration + def android + String buildApkPath + String outputFolder + def signconfig + + public TinkerPatchSchemaTask() { + description = 'Assemble Tinker Patch' + group = 'tinker' + outputs.upToDateWhen { false } + configuration = project.tinkerPatch + + android = project.extensions.android + } + + + @TaskAction + def tinkerPatch() { +// println configuration.toString() + + configuration.checkParameter() + configuration.buildConfig.checkParameter() + configuration.res.checkParameter() + configuration.dex.checkDexMode() + configuration.sevenZip.resolveZipFinalPath() + + InputParam.Builder builder = new InputParam.Builder() + if (configuration.useSign) { + if (signconfig == null) { + throw new GradleException("can't the get signconfig for ${taskName} build") + } + builder.setSignFile(signconfig.storeFile) + .setKeypass(signconfig.keyPassword) + .setStorealias(signconfig.keyAlias) + .setStorepass(signconfig.storePassword) + + } + + builder.setOldApk(configuration.oldApk) + .setNewApk(buildApkPath) + .setOutBuilder(outputFolder) + .setIgnoreWarning(configuration.ignoreWarning) + .setDexFilePattern(configuration.dex.pattern) + .setDexLoaderPattern(configuration.dex.loader) + .setDexMode(configuration.dex.dexMode) + .setSoFilePattern(configuration.lib.pattern) + .setResourceFilePattern(configuration.res.pattern) + .setResourceIgnoreChangePattern(configuration.res.ignoreChange) + .setResourceLargeModSize(configuration.res.largeModSize) + .setUseApplyResource(configuration.buildConfig.usingResourceMapping) + .setConfigFields(configuration.packageConfig.getFields()) + .setSevenZipPath(configuration.sevenZip.path) + .setUseSign(configuration.useSign) + + InputParam inputParam = builder.create() + Runner.gradleRun(inputParam); + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerProguardConfigTask.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerProguardConfigTask.groovy new file mode 100644 index 00000000..c0f9e0d0 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerProguardConfigTask.groovy @@ -0,0 +1,102 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.task + +import com.tencent.tinker.build.gradle.TinkerPatchPlugin +import com.tencent.tinker.build.util.FileOperation +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +/** + * The configuration properties. + * + * @author zhangshaowen + */ +public class TinkerProguardConfigTask extends DefaultTask { + static final String PROGUARD_CONFIG_PATH = TinkerPatchPlugin.TINKER_INTERMEDIATES + "tinker_proguard.pro" + static final String PROGUARD_CONFIG_SETTINGS = + "-keepattributes *Annotation* \n" + + "-dontwarn com.tencent.tinker.anno.AnnotationProcessor \n" + + "-keep @com.tencent.tinker.anno.DefaultLifeCycle public class *\n" + + "-keep public class * extends android.app.Application {\n" + + " *;\n" + + "}\n" + + "\n" + + "-keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + + " *;\n" + + "}\n" + + "-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + + " *;\n" + + "}\n" + + "\n" + + "-keep public class com.tencent.tinker.loader.TinkerLoader {\n" + + " *;\n" + + "}\n" + + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + + " *;\n" + + "}\n" + + "-keep public class com.tencent.tinker.loader.TinkerTestDexLoad {\n" + + " *;\n" + + "}\n" + + "\n" + + + def applicationVariant + boolean shouldApplyMapping = true; + + + public TinkerProguardConfigTask() { + group = 'tinker' + } + + @TaskAction + def updateTinkerProguardConfig() { + def file = project.file(PROGUARD_CONFIG_PATH) + project.logger.error("try update tinker proguard file with ${file}") + + // Create the directory if it doesnt exist already + file.getParentFile().mkdirs() + + // Write our recommended proguard settings to this file + FileWriter fr = new FileWriter(file.path) + + String applyMappingFile = project.extensions.tinkerPatch.buildConfig.applyMapping + + //write applymapping + if (shouldApplyMapping && FileOperation.isLegalFile(applyMappingFile)) { + project.logger.error("try add applymapping ${applyMappingFile} to build the package") + fr.write("-applymapping " + applyMappingFile) + fr.write("\n") + } else { + project.logger.error("applymapping file ${applyMappingFile} is illegal, just ignore") + } + + fr.write(PROGUARD_CONFIG_SETTINGS) + fr.write("#your dex.loader patterns here\n") + //they will removed when apply + Iterable loader = project.extensions.tinkerPatch.dex.loader + for (String pattern : loader) { + fr.write("-keep class " + pattern) + fr.write("\n") + } + fr.close() + // Add this proguard settings file to the list + applicationVariant.getBuildType().buildType.proguardFiles(file) + def files = applicationVariant.getBuildType().buildType.getProguardFiles() + project.logger.error("now proguard files is ${files}") + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerResourceIdTask.groovy b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerResourceIdTask.groovy new file mode 100644 index 00000000..4e51c6a0 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerResourceIdTask.groovy @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.gradle.task + +import com.tencent.tinker.build.aapt.AaptResourceCollector +import com.tencent.tinker.build.aapt.AaptUtil +import com.tencent.tinker.build.aapt.PatchUtil +import com.tencent.tinker.build.aapt.RDotTxtEntry +import com.tencent.tinker.build.gradle.TinkerPatchPlugin +import com.tencent.tinker.build.util.FileOperation +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.tasks.TaskAction + +/** + * The configuration properties. + * + * @author zhangshaowen + */ +public class TinkerResourceIdTask extends DefaultTask { + static final String RESOURCE_PUBLIC_XML = TinkerPatchPlugin.TINKER_INTERMEDIATES + "public.xml" + static final String RESOURCE_IDX_XML = TinkerPatchPlugin.TINKER_INTERMEDIATES + "idx.xml" + + String resDir + + TinkerResourceIdTask() { + group = 'tinker' + } + + @TaskAction + def applyResourceId() { + // Parse the public.xml and ids.xml + String idsXml = resDir + "/values/ids.xml"; + String publicXml = resDir + "/values/public.xml"; + FileOperation.deleteFile(idsXml); + FileOperation.deleteFile(publicXml); + List resourceDirectoryList = new ArrayList() + resourceDirectoryList.add(resDir) + Map> rTypeResourceMap = null + + String resourceMappingFile = project.extensions.tinkerPatch.buildConfig.applyResourceMapping + + if (FileOperation.isLegalFile(resourceMappingFile)) { + project.logger.error("we build ${project.getName()} apk with apply resource mapping file ${resourceMappingFile}") + project.extensions.tinkerPatch.buildConfig.usingResourceMapping = true + rTypeResourceMap = PatchUtil.readRTxt(resourceMappingFile); + } else { + project.logger.error("apply resource mapping file ${resourceMappingFile} is illegal, just ignore") + } + AaptResourceCollector aaptResourceCollector = AaptUtil.collectResource(resourceDirectoryList, rTypeResourceMap) + PatchUtil.generatePublicResourceXml(aaptResourceCollector, idsXml, publicXml) + File publicFile = new File(publicXml) + if (publicFile.exists()) { + FileOperation.copyFileUsingStream(publicFile, project.file(RESOURCE_PUBLIC_XML)) + project.logger.error("tinker gen resource public.xml in ${RESOURCE_PUBLIC_XML}") + } + File idxFile = new File(idsXml) + if (idxFile.exists()) { + FileOperation.copyFileUsingStream(idxFile, project.file(RESOURCE_IDX_XML)) + project.logger.error("tinker gen resource idx.xml in ${RESOURCE_IDX_XML}") + } + } +} + diff --git a/tinker-build/tinker-patch-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.tencent.tinker.patch.properties b/tinker-build/tinker-patch-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.tencent.tinker.patch.properties new file mode 100644 index 00000000..2e40e3b5 --- /dev/null +++ b/tinker-build/tinker-patch-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.tencent.tinker.patch.properties @@ -0,0 +1 @@ +implementation-class=com.tencent.tinker.build.gradle.TinkerPatchPlugin \ No newline at end of file diff --git a/tinker-build/tinker-patch-lib/.gitignore b/tinker-build/tinker-patch-lib/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-build/tinker-patch-lib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-build/tinker-patch-lib/build.gradle b/tinker-build/tinker-patch-lib/build.gradle new file mode 100644 index 00000000..f0443dce --- /dev/null +++ b/tinker-build/tinker-patch-lib/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'java' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':tinker-commons') + compile group: 'net.dongliu', name: 'apk-parser', version: '2.1.2' + compile group: 'com.google.guava', name: 'guava', version: '11.0.2' +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + + resources { + srcDir 'src/main/resources' + } + } +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/tinker-build/tinker-patch-lib/gradle.properties b/tinker-build/tinker-patch-lib/gradle.properties new file mode 100644 index 00000000..0e35ccb4 --- /dev/null +++ b/tinker-build/tinker-patch-lib/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=tinker-patch-lib +POM_NAME=Tinker Patch Lib +POM_PACKAGING=jar \ No newline at end of file diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptResourceCollector.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptResourceCollector.java new file mode 100644 index 00000000..97bc68c4 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptResourceCollector.java @@ -0,0 +1,346 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import com.google.common.base.Joiner; +import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; +import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class AaptResourceCollector { + + private final Map>> rTypeResourceDirectoryMap; + //private final Map> rTypeIncreaseResourceDirectoryListMap; +// private final Map> rTypeIncreaseResourceDirectoryMap; + private final Map rTypeEnumeratorMap; + private final Map originalResourceMap; + private final Map> rTypeResourceMap; + private final Map> rTypeIncreaseResourceMap; + private final Map> duplicateResourceMap; + private final Map sanitizeNameMap; + private final Set ignoreIdSet; + private int currentTypeId; + + public AaptResourceCollector() { + this.rTypeResourceDirectoryMap = new HashMap>>(); +// this.rTypeIncreaseResourceDirectoryListMap = new HashMap>(); +// this.rTypeIncreaseResourceDirectoryMap = new HashMap>(); + this.rTypeEnumeratorMap = new HashMap(); + this.rTypeResourceMap = new HashMap>(); + this.rTypeIncreaseResourceMap = new HashMap>(); + this.duplicateResourceMap = new HashMap>(); + this.sanitizeNameMap = new HashMap(); + this.originalResourceMap = new HashMap(); + this.ignoreIdSet = new HashSet(); + //attr type must 1 + this.currentTypeId = 2; + } + + public AaptResourceCollector(Map> rTypeResourceMap) { + this(); + if (rTypeResourceMap != null) { + Iterator>> iterator = rTypeResourceMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + RType rType = entry.getKey(); + Set set = entry.getValue(); +// this.rTypeResourceMap.put(rType, new HashSet(set)); + for (RDotTxtEntry rDotTxtEntry : set) { + originalResourceMap.put(rDotTxtEntry, rDotTxtEntry); + ResourceIdEnumerator resourceIdEnumerator = null; + if (!rDotTxtEntry.idType.equals(IdType.INT_ARRAY)) { + int resourceId = Integer.decode(rDotTxtEntry.idValue).intValue(); + int typeId = ((resourceId & 0x00FF0000) / 0x00010000); + if (typeId >= currentTypeId) { + currentTypeId = typeId + 1; + } + if (this.rTypeEnumeratorMap.containsKey(rType)) { + resourceIdEnumerator = this.rTypeEnumeratorMap.get(rType); + if (resourceIdEnumerator.currentId < resourceId) { + resourceIdEnumerator.currentId = resourceId; + } + } else { + resourceIdEnumerator = new ResourceIdEnumerator(); + resourceIdEnumerator.currentId = resourceId; + this.rTypeEnumeratorMap.put(rType, resourceIdEnumerator); + } + } + } + } + } + } + + public void addIntResourceIfNotPresent(RType rType, String name) { //, ResourceDirectory resourceDirectory) { + if (!rTypeEnumeratorMap.containsKey(rType)) { + if (rType.equals(RType.ATTR)) { + rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(1)); + } else { + rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(currentTypeId++)); + } + } + + RDotTxtEntry entry = new FakeRDotTxtEntry(IdType.INT, rType, name); + Set resourceSet = null; + if (this.rTypeResourceMap.containsKey(rType)) { + resourceSet = this.rTypeResourceMap.get(rType); + } else { + resourceSet = new HashSet(); + this.rTypeResourceMap.put(rType, resourceSet); + } + if (!resourceSet.contains(entry)) { + String idValue = String.format("0x%08x", rTypeEnumeratorMap.get(rType).next()); + addResource(rType, IdType.INT, name, idValue); //, resourceDirectory); + } + } + + public void addIntArrayResourceIfNotPresent(RType rType, String name, int numValues) { + // Robolectric expects the array to be populated with the right number + // of values, irrespective + // of what the values are. + String idValue = String.format("{ %s }", Joiner.on(",").join(Collections.nCopies(numValues, "0x7f000000"))); + addResource(rType, IdType.INT_ARRAY, name, idValue); + } + + /** + * add resource + * + * @param rType + * @param idType + * @param name + * @param idValue + */ + public void addResource(RType rType, IdType idType, String name, String idValue) { + Set resourceSet = null; + if (this.rTypeResourceMap.containsKey(rType)) { + resourceSet = this.rTypeResourceMap.get(rType); + } else { + resourceSet = new HashSet(); + this.rTypeResourceMap.put(rType, resourceSet); + } + RDotTxtEntry rDotTxtEntry = new RDotTxtEntry(idType, rType, name, idValue); + boolean increaseResource = false; + if (!resourceSet.contains(rDotTxtEntry)) { + if (this.originalResourceMap.containsKey(rDotTxtEntry)) { + this.rTypeEnumeratorMap.get(rType).previous(); + rDotTxtEntry = this.originalResourceMap.get(rDotTxtEntry); + } else { + increaseResource = true; + } + resourceSet.add(rDotTxtEntry); + } + Set increaseResourceSet = null; + //new r dot txt entry + if (this.rTypeIncreaseResourceMap.containsKey(rType)) { + increaseResourceSet = this.rTypeIncreaseResourceMap.get(rType); + } else { + increaseResourceSet = new HashSet(); + this.rTypeIncreaseResourceMap.put(rType, increaseResourceSet); + } + if (increaseResource) { + increaseResourceSet.add(rDotTxtEntry); +//addResourceDirectory(rType, name, resourceDirectory); + } + } + +//private void addResourceDirectory(RType rType,String name, ResourceDirectory resourceDirectory){ +//if(resourceDirectory!=null){ +//Map resourceDirectoryMap=null; +//List resourceDirectoryList=null; +//if(this.rTypeIncreaseResourceDirectoryMap.containsKey(rType)){ +//resourceDirectoryMap=this.rTypeIncreaseResourceDirectoryMap.get(rType); +//resourceDirectoryList=this.rTypeIncreaseResourceDirectoryListMap.get(rType); +//}else{ +//resourceDirectoryMap=new HashMap(); +//this.rTypeIncreaseResourceDirectoryMap.put(rType, resourceDirectoryMap); +//resourceDirectoryList=new ArrayList(); +//this.rTypeIncreaseResourceDirectoryListMap.put(rType, resourceDirectoryList); +//} +//ResourceDirectory existResourceDirectory=null; +//if(resourceDirectoryMap.containsKey(resourceDirectory)){ +//existResourceDirectory=resourceDirectoryMap.get(resourceDirectory); +//}else{ +//existResourceDirectory=resourceDirectory; +//resourceDirectoryMap.put(resourceDirectory, resourceDirectory); +//resourceDirectoryList.add(existResourceDirectory); +//} +//existResourceDirectory.resourceEntrySet.add(new ResourceEntry(name,null)); +//} +//} + + /** + * is contain resource + * + * @param rType + * @param idType + * @param name + * @return boolean + */ + public boolean isContainResource(RType rType, IdType idType, String name) { + boolean result = false; + if (this.rTypeResourceMap.containsKey(rType)) { + Set resourceSet = this.rTypeResourceMap.get(rType); + if (resourceSet.contains(new RDotTxtEntry(idType, rType, name, "0x7f000000"))) { + result = true; + } + } + return result; + } + + /** + * add r type resource name + * + * @param rType + * @param resourceName + * @param resourceDirectory + */ + void addRTypeResourceName(RType rType, String resourceName, String resourceValue, ResourceDirectory resourceDirectory) { + Map> directoryResourceDirectoryMap = null; + if (this.rTypeResourceDirectoryMap.containsKey(rType)) { + directoryResourceDirectoryMap = this.rTypeResourceDirectoryMap.get(rType); + } else { + directoryResourceDirectoryMap = new HashMap>(); + this.rTypeResourceDirectoryMap.put(rType, directoryResourceDirectoryMap); + } + Set resourceDirectorySet = null; + if (directoryResourceDirectoryMap.containsKey(resourceDirectory.directoryName)) { + resourceDirectorySet = directoryResourceDirectoryMap.get(resourceDirectory.directoryName); + } else { + resourceDirectorySet = new HashSet(); + directoryResourceDirectoryMap.put(resourceDirectory.directoryName, resourceDirectorySet); + } + boolean find = false; + ResourceDirectory newResourceDirectory = new ResourceDirectory(resourceDirectory.directoryName, resourceDirectory.resourceFullFilename); + if (!resourceDirectorySet.contains(newResourceDirectory)) { + resourceDirectorySet.add(newResourceDirectory); + } + for (ResourceDirectory oldResourceDirectory : resourceDirectorySet) { + if (oldResourceDirectory.resourceEntrySet.contains(new ResourceEntry(resourceName, resourceValue))) { + find = true; + String resourceKey = rType + "/" + resourceDirectory.directoryName + "/" + resourceName; + Set fullFilenameSet = null; + if (!this.duplicateResourceMap.containsKey(resourceKey)) { + fullFilenameSet = new HashSet(); + fullFilenameSet.add(oldResourceDirectory.resourceFullFilename); + this.duplicateResourceMap.put(resourceKey, fullFilenameSet); + } else { + fullFilenameSet = this.duplicateResourceMap.get(resourceKey); + } + fullFilenameSet.add(resourceDirectory.resourceFullFilename); + } + } + if (!find) { + for (ResourceDirectory oldResourceDirectory : resourceDirectorySet) { + if (oldResourceDirectory.equals(newResourceDirectory)) { + if (!oldResourceDirectory.resourceEntrySet.contains(new ResourceEntry(resourceName, resourceValue))) { + oldResourceDirectory.resourceEntrySet.add(new ResourceEntry(resourceName, resourceValue)); + } + } + } + } + } + + void putSanitizeName(String sanitizeName, String rawName) { + if (!this.sanitizeNameMap.containsKey(sanitizeName)) { + this.sanitizeNameMap.put(sanitizeName, rawName); + } + } + + /** + * get raw name + * + * @param sanitizeName + * @return String + */ + public String getRawName(String sanitizeName) { + return this.sanitizeNameMap.get(sanitizeName); + } + + /** + * get r type resource map + * + * @return Map> + */ + public Map> getRTypeResourceMap() { + return this.rTypeResourceMap; + } + + /** + * @return the duplicateResourceMap + */ + public Map> getDuplicateResourceMap() { + return duplicateResourceMap; + } + + /** + * @return the rTypeIncreaseResourceMap + */ + public Map> getRTypeIncreaseResourceMap() { + return rTypeIncreaseResourceMap; + } + + /** + * @return the rTypeResourceDirectoryMap + */ + public Map>> getRTypeResourceDirectoryMap() { + return rTypeResourceDirectoryMap; + } + +///** +// * @return the rTypeIncreaseResourceDirectoryListMap +// */ +//public Map> getRTypeIncreaseResourceDirectoryListMap() { +//return rTypeIncreaseResourceDirectoryListMap; +//} + + void addIgnoreId(String name) { + ignoreIdSet.add(name); + } + + /** + * @return the ignoreIdSet + */ + public Set getIgnoreIdSet() { + return ignoreIdSet; + } + + private static class ResourceIdEnumerator { + + private int currentId = 0; + + ResourceIdEnumerator() { + } + + ResourceIdEnumerator(int typeId) { + this.currentId = 0x7f000000 + 0x10000 * typeId + -1; + } + + int previous() { + return --currentId; + } + + int next() { + return ++currentId; + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptUtil.java new file mode 100644 index 00000000..e7172f4f --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptUtil.java @@ -0,0 +1,489 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; +import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +public final class AaptUtil { + + private static final String ID_DEFINITION_PREFIX = "@+id/"; + private static final String ITEM_TAG = "item"; + + private static final XPathExpression ANDROID_ID_USAGE = createExpression("//@*[starts-with(., '@') and " + "not(starts-with(., '@+')) and " + "not(starts-with(., '@android:')) and " + "not(starts-with(., '@null'))]"); + + private static final XPathExpression ANDROID_ID_DEFINITION = createExpression("//@*[starts-with(., '@+') and " + "not(starts-with(., '@+android:id'))]"); + + private static final Map RESOURCE_TYPES = getResourceTypes(); + private static final List IGNORED_TAGS = Arrays.asList("eat-comment", "skip"); + + private static XPathExpression createExpression(String expressionStr) { + try { + return XPathFactory.newInstance().newXPath().compile(expressionStr); + } catch (XPathExpressionException e) { + throw new AaptUtilException(e); + } + } + + private static Map getResourceTypes() { + Map types = new HashMap(); + for (RType rType : RType.values()) { + types.put(rType.toString(), rType); + } + types.put("string-array", RType.ARRAY); + types.put("integer-array", RType.ARRAY); + types.put("declare-styleable", RType.STYLEABLE); + return types; + } + + public static AaptResourceCollector collectResource(List resourceDirectoryList) { + return collectResource(resourceDirectoryList, null); + } + + public static AaptResourceCollector collectResource(List resourceDirectoryList, Map> rTypeResourceMap) { + AaptResourceCollector resourceCollector = new AaptResourceCollector(rTypeResourceMap); + List references = new ArrayList(); + for (String resourceDirectory : resourceDirectoryList) { + try { + collectResources(resourceDirectory, resourceCollector); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + for (String resourceDirectory : resourceDirectoryList) { + try { + processXmlFilesForIds(resourceDirectory, references, resourceCollector); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return resourceCollector; + } + + public static void processXmlFilesForIds(String resourceDirectory, List references, AaptResourceCollector resourceCollector) throws Exception { + List xmlFullFilenameList = FileUtil.findMatchFile(resourceDirectory, Constant.Symbol.DOT + Constant.File.XML); + if (xmlFullFilenameList != null) { + for (String xmlFullFilename : xmlFullFilenameList) { + File xmlFile = new File(xmlFullFilename); + String parentFullFilename = xmlFile.getParent(); + File parentFile = new File(parentFullFilename); + if (isAValuesDirectory(parentFile.getName())) { + // Ignore files under values* directories. + continue; + } + processXmlFile(xmlFullFilename, references, resourceCollector); + } + } + } + + private static void collectResources(String resourceDirectory, AaptResourceCollector resourceCollector) throws Exception { + File resourceDirectoryFile = new File(resourceDirectory); + File[] fileArray = resourceDirectoryFile.listFiles(); + if (fileArray != null) { + for (File file : fileArray) { + if (file.isDirectory()) { + String directoryName = file.getName(); + if (directoryName.startsWith("values")) { + if (!isAValuesDirectory(directoryName)) { + throw new AaptUtilException("'" + directoryName + "' is not a valid values directory."); + } + processValues(file.getAbsolutePath(), resourceCollector); + } else { + processFileNamesInDirectory(file.getAbsolutePath(), resourceCollector); + } + } + } + } + } + + /** + * is a value directory + * + * @param directoryName + * @return boolean + */ + public static boolean isAValuesDirectory(String directoryName) { + if (directoryName == null) { + throw new NullPointerException("directoryName can not be null"); + } + return directoryName.equals("values") || directoryName.startsWith("values-"); + } + + public static void processFileNamesInDirectory(String resourceDirectory, AaptResourceCollector resourceCollector) throws IOException { + File resourceDirectoryFile = new File(resourceDirectory); + String directoryName = resourceDirectoryFile.getName(); + int dashIndex = directoryName.indexOf('-'); + if (dashIndex != -1) { + directoryName = directoryName.substring(0, dashIndex); + } + + if (!RESOURCE_TYPES.containsKey(directoryName)) { + throw new AaptUtilException(resourceDirectoryFile.getAbsolutePath() + " is not a valid resource sub-directory."); + } + File[] fileArray = resourceDirectoryFile.listFiles(); + if (fileArray != null) { + for (File file : fileArray) { + if (file.isHidden()) { + continue; + } + String filename = file.getName(); + int dotIndex = filename.indexOf('.'); + String resourceName = dotIndex != -1 ? filename.substring(0, dotIndex) : filename; + + RType rType = RESOURCE_TYPES.get(directoryName); + resourceCollector.addIntResourceIfNotPresent(rType, resourceName); + com.tencent.tinker.build.aapt.ResourceDirectory resourceDirectoryBean = new com.tencent.tinker.build.aapt.ResourceDirectory(file.getParentFile().getName(), file.getAbsolutePath()); + resourceCollector.addRTypeResourceName(rType, resourceName, null, resourceDirectoryBean); + } + } + } + + public static void processValues(String resourceDirectory, AaptResourceCollector resourceCollector) throws Exception { + File resourceDirectoryFile = new File(resourceDirectory); + File[] fileArray = resourceDirectoryFile.listFiles(); + if (fileArray != null) { + for (File file : fileArray) { + if (file.isHidden()) { + continue; + } + if (!file.isFile()) { + // warning + continue; + } + processValuesFile(file.getAbsolutePath(), resourceCollector); + } + } + } + + public static void processValuesFile(String valuesFullFilename, AaptResourceCollector resourceCollector) throws Exception { + Document document = JavaXmlUtil.parse(valuesFullFilename); + String directoryName = new File(valuesFullFilename).getParentFile().getName(); + Element root = document.getDocumentElement(); + + for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + String resourceType = node.getNodeName(); + if (resourceType.equals(ITEM_TAG)) { + resourceType = node.getAttributes().getNamedItem("type").getNodeValue(); + if (resourceType.equals("id")) { + resourceCollector.addIgnoreId(node.getAttributes().getNamedItem("name").getNodeValue()); + } + } + + if (IGNORED_TAGS.contains(resourceType)) { + continue; + } + + if (!RESOURCE_TYPES.containsKey(resourceType)) { + throw new AaptUtilException("Invalid resource type '<" + resourceType + ">' in '" + valuesFullFilename + "'."); + } + + RType rType = RESOURCE_TYPES.get(resourceType); + String resourceValue = null; + switch (rType) { + case STRING: + case COLOR: + case DIMEN: + case DRAWABLE: + case BOOL: + resourceValue = node.getTextContent().trim(); + break; + case ARRAY://has sub item + case PLURALS://has sub item + case STYLE://has sub item + case STYLEABLE://has sub item + resourceValue = subNodeToString(node); + break; + case FRACTION://no sub item + resourceValue = nodeToString(node, true); + break; + case ATTR://no sub item + resourceValue = nodeToString(node, true); + break; + } + try { + addToResourceCollector(resourceCollector, new com.tencent.tinker.build.aapt.ResourceDirectory(directoryName, valuesFullFilename), node, rType, resourceValue); + } catch (Exception e) { + throw new AaptUtilException(e.getMessage() + ",Process file error:" + valuesFullFilename, e); + } + } + } + + public static void processXmlFile(String xmlFullFilename, List references, AaptResourceCollector resourceCollector) throws IOException, XPathExpressionException { + Document document = JavaXmlUtil.parse(xmlFullFilename); + NodeList nodesWithIds = (NodeList) ANDROID_ID_DEFINITION.evaluate(document, XPathConstants.NODESET); + for (int i = 0; i < nodesWithIds.getLength(); i++) { + String resourceName = nodesWithIds.item(i).getNodeValue(); + if (!resourceName.startsWith(ID_DEFINITION_PREFIX)) { + throw new AaptUtilException("Invalid definition of a resource: '" + resourceName + "'"); + } + + resourceCollector.addIntResourceIfNotPresent(RType.ID, resourceName.substring(ID_DEFINITION_PREFIX.length())); + } + + NodeList nodesUsingIds = (NodeList) ANDROID_ID_USAGE.evaluate(document, XPathConstants.NODESET); + for (int i = 0; i < nodesUsingIds.getLength(); i++) { + String resourceName = nodesUsingIds.item(i).getNodeValue(); + int slashPosition = resourceName.indexOf('/'); + + String rawRType = resourceName.substring(1, slashPosition); + String name = resourceName.substring(slashPosition + 1); + + if (name.startsWith("android:")) { + continue; + } + if (!RESOURCE_TYPES.containsKey(rawRType)) { + throw new AaptUtilException("Invalid reference '" + resourceName + "' in '" + xmlFullFilename + "'"); + } + RType rType = RESOURCE_TYPES.get(rawRType); + +//if(!resourceCollector.isContainResource(rType, IdType.INT, sanitizeName(resourceCollector, name))){ +//throw new AaptUtilException("Not found reference '" + resourceName + "' in '" + xmlFullFilename + "'"); +//} + references.add(new com.tencent.tinker.build.aapt.FakeRDotTxtEntry(IdType.INT, rType, sanitizeName(resourceCollector, name))); + } + } + + private static void addToResourceCollector(AaptResourceCollector resourceCollector, com.tencent.tinker.build.aapt.ResourceDirectory resourceDirectory, Node node, RType rType, String resourceValue) { + String resourceName = sanitizeName(resourceCollector, extractNameAttribute(node)); + resourceCollector.addRTypeResourceName(rType, resourceName, resourceValue, resourceDirectory); + if (rType.equals(RType.STYLEABLE)) { + + int count = 0; + for (Node attrNode = node.getFirstChild(); attrNode != null; attrNode = attrNode.getNextSibling()) { + if (attrNode.getNodeType() != Node.ELEMENT_NODE || !attrNode.getNodeName().equals("attr")) { + continue; + } + + String rawAttrName = extractNameAttribute(attrNode); + String attrName = sanitizeName(resourceCollector, rawAttrName); + resourceCollector.addResource(RType.STYLEABLE, IdType.INT, String.format("%s_%s", resourceName, attrName), Integer.toString(count++)); + + if (!rawAttrName.startsWith("android:")) { + resourceCollector.addIntResourceIfNotPresent(RType.ATTR, attrName); + resourceCollector.addRTypeResourceName(RType.ATTR, rawAttrName, nodeToString(attrNode, true), resourceDirectory); + } + } + + resourceCollector.addIntArrayResourceIfNotPresent(rType, resourceName, count); + } else { + resourceCollector.addIntResourceIfNotPresent(rType, resourceName); + } + } + + private static String sanitizeName(AaptResourceCollector resourceCollector, String rawName) { + String sanitizeName = rawName.replaceAll("[.:]", "_"); + resourceCollector.putSanitizeName(sanitizeName, rawName); + return sanitizeName; + } + + private static String extractNameAttribute(Node node) { + return node.getAttributes().getNamedItem("name").getNodeValue(); + } + + /** + * merge package r type resource map + * + * @param packageRTypeResourceMapList + * @return Map>> + */ + public static Map>> mergePackageRTypeResourceMap(List packageRTypeResourceMapList) { + Map>> packageRTypeResourceMergeMap = new HashMap>>(); + Map aaptResourceCollectorMap = new HashMap(); + for (PackageRTypeResourceMap packageRTypeResourceMap : packageRTypeResourceMapList) { + String packageName = packageRTypeResourceMap.packageName; + Map> rTypeResourceMap = packageRTypeResourceMap.rTypeResourceMap; + AaptResourceCollector aaptResourceCollector = null; + if (aaptResourceCollectorMap.containsKey(packageName)) { + aaptResourceCollector = aaptResourceCollectorMap.get(packageName); + } else { + aaptResourceCollector = new AaptResourceCollector(); + aaptResourceCollectorMap.put(packageName, aaptResourceCollector); + } + Iterator>> iterator = rTypeResourceMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + RType rType = entry.getKey(); + Set rDotTxtEntrySet = entry.getValue(); + for (com.tencent.tinker.build.aapt.RDotTxtEntry rDotTxtEntry : rDotTxtEntrySet) { + if (rDotTxtEntry.idType.equals(IdType.INT)) { + aaptResourceCollector.addIntResourceIfNotPresent(rType, rDotTxtEntry.name); + } else if (rDotTxtEntry.idType.equals(IdType.INT_ARRAY)) { + aaptResourceCollector.addResource(rType, rDotTxtEntry.idType, rDotTxtEntry.name, rDotTxtEntry.idValue); + } + } + } + } + Iterator> iterator = aaptResourceCollectorMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + packageRTypeResourceMergeMap.put(entry.getKey(), entry.getValue().getRTypeResourceMap()); + } + return packageRTypeResourceMergeMap; + } + + /** + * write R.java + * + * @param outputDirectory + * @param packageName + * @param rTypeResourceMap + * @param isFinal + */ + public static void writeRJava(String outputDirectory, String packageName, Map> rTypeResourceMap, boolean isFinal) { + String outputFullFilename = new File(outputDirectory).getAbsolutePath() + Constant.Symbol.SLASH_LEFT + (packageName.replace(Constant.Symbol.DOT, Constant.Symbol.SLASH_LEFT) + Constant.Symbol.SLASH_LEFT + "R" + Constant.Symbol.DOT + Constant.File.JAVA); + FileUtil.createFile(outputFullFilename); + PrintWriter writer = null; + try { + writer = new PrintWriter(new FileOutputStream(outputFullFilename)); + writer.format("package %s;\n\n", packageName); + writer.println("public final class R {\n"); + for (RType rType : rTypeResourceMap.keySet()) { + // Now start the block for the new type. + writer.format(" public static final class %s {\n", rType.toString()); + for (com.tencent.tinker.build.aapt.RDotTxtEntry rDotTxtEntry : rTypeResourceMap.get(rType)) { + // Write out the resource. + // Write as an int. + writer.format(" public static%s%s %s=%s;\n", isFinal ? " final " : " ", rDotTxtEntry.idType, rDotTxtEntry.name, rDotTxtEntry.idValue); + } + writer.println(" }\n"); + } + // Close the class definition. + writer.println("}"); + } catch (Exception e) { + throw new AaptUtilException(e); + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + } + + /** + * write R.java + * + * @param outputDirectory + * @param packageRTypeResourceMap + * @param isFinal + * @throws IOException + */ + public static void writeRJava(String outputDirectory, Map>> packageRTypeResourceMap, boolean isFinal) { + for (String packageName : packageRTypeResourceMap.keySet()) { + Map> rTypeResourceMap = packageRTypeResourceMap.get(packageName); + writeRJava(outputDirectory, packageName, rTypeResourceMap, isFinal); + } + } + + private static String subNodeToString(Node node) { + StringBuilder stringBuilder = new StringBuilder(); + if (node != null) { + NodeList nodeList = node.getChildNodes(); + stringBuilder.append(nodeToString(node, false)); + stringBuilder.append(StringUtil.CRLF_STRING); + int nodeListLength = nodeList.getLength(); + for (int i = 0; i < nodeListLength; i++) { + Node childNode = nodeList.item(i); + if (childNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + stringBuilder.append(nodeToString(childNode, true)); + stringBuilder.append(StringUtil.CRLF_STRING); + } + if (stringBuilder.length() > StringUtil.CRLF_STRING.length()) { + stringBuilder.delete(stringBuilder.length() - StringUtil.CRLF_STRING.length(), stringBuilder.length()); + } + } + return stringBuilder.toString(); + } + + private static String nodeToString(Node node, boolean isNoChild) { + StringBuilder stringBuilder = new StringBuilder(); + if (node != null) { + stringBuilder.append(node.getNodeName()); + NamedNodeMap namedNodeMap = node.getAttributes(); + stringBuilder.append(Constant.Symbol.MIDDLE_BRACKET_LEFT); + int namedNodeMapLength = namedNodeMap.getLength(); + for (int j = 0; j < namedNodeMapLength; j++) { + Node attributeNode = namedNodeMap.item(j); + stringBuilder.append(Constant.Symbol.AT + attributeNode.getNodeName() + Constant.Symbol.EQUAL + attributeNode.getNodeValue()); + if (j < namedNodeMapLength - 1) { + stringBuilder.append(Constant.Symbol.COMMA); + } + } + stringBuilder.append(Constant.Symbol.MIDDLE_BRACKET_RIGHT); + String value = StringUtil.nullToBlank(isNoChild ? node.getTextContent() : node.getNodeValue()).trim(); + if (StringUtil.isNotBlank(value)) { + stringBuilder.append(Constant.Symbol.EQUAL + value); + } + } + return stringBuilder.toString(); + } + + public static class PackageRTypeResourceMap { + private String packageName = null; + private Map> rTypeResourceMap = null; + + public PackageRTypeResourceMap(String packageName, Map> rTypeResourceMap) { + this.packageName = packageName; + this.rTypeResourceMap = rTypeResourceMap; + } + } + + public static class AaptUtilException extends RuntimeException { + private static final long serialVersionUID = 1702278793911780809L; + + public AaptUtilException(String message) { + super(message); + } + + public AaptUtilException(Throwable cause) { + super(cause); + } + + public AaptUtilException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Constant.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Constant.java new file mode 100644 index 00000000..e0c85531 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Constant.java @@ -0,0 +1,302 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +public interface Constant { + + interface Base { + String EXCEPTION = "exception"; + } + + interface Symbol { + /** + * dot "." + */ + String DOT = "."; + char DOT_CHAR = '.'; + /** + * comma "," + */ + String COMMA = ","; + /** + * colon ":" + */ + String COLON = ":"; + /** + * semicolon ";" + */ + String SEMICOLON = ";"; + /** + * equal "=" + */ + String EQUAL = "="; + /** + * and "&" + */ + String AND = "&"; + /** + * question mark "?" + */ + String QUESTION_MARK = "?"; + /** + * wildcard "*" + */ + String WILDCARD = "*"; + /** + * underline "_" + */ + String UNDERLINE = "_"; + /** + * at "@" + */ + String AT = "@"; + /** + * minus "-" + */ + String MINUS = "-"; + /** + * logic and "&&" + */ + String LOGIC_AND = "&&"; + /** + * logic or "||" + */ + String LOGIC_OR = "||"; + /** + * brackets begin "(" + */ + String BRACKET_LEFT = "("; + /** + * brackets end ")" + */ + String BRACKET_RIGHT = ")"; + /** + * middle bracket left "[" + */ + String MIDDLE_BRACKET_LEFT = "["; + /** + * middle bracket right "]" + */ + String MIDDLE_BRACKET_RIGHT = "]"; + /** + * big bracket "{" + */ + String BIG_BRACKET_LEFT = "{"; + /** + * big bracket "}" + */ + String BIG_BRACKET_RIGHT = "}"; + /** + * slash "/" + */ + String SLASH_LEFT = "/"; + /** + * slash "\" + */ + String SLASH_RIGHT = "\\"; + /** + * xor or regex begin "^" + */ + String XOR = "^"; + /** + * dollar or regex end "$" + */ + String DOLLAR = "$"; + /** + * single quotes "'" + */ + String SINGLE_QUOTES = "'"; + /** + * double quotes "\"" + */ + String DOUBLE_QUOTES = "\""; + } + + interface Encoding { + /** + * encoding + */ + String ISO88591 = "ISO-8859-1"; + String GB2312 = "GB2312"; + String GBK = "GBK"; + String UTF8 = "UTF-8"; + } + + interface Timezone { + String ASIA_SHANGHAI = "Asia/Shanghai"; + } + + interface Http { + + interface RequestMethod { + /** + * for request method + */ + String PUT = "PUT"; + String DELETE = "DELETE"; + String GET = "GET"; + String POST = "POST"; + String HEAD = "HEAD"; + String OPTIONS = "OPTIONS"; + String TRACE = "TRACE"; + } + + interface HeaderKey { + /** + * for request,response header + */ + String CONTENT_TYPE = "Content-Type"; + String CONTENT_DISPOSITION = "Content-Disposition"; + String ACCEPT_CHARSET = "Accept-Charset"; + String CONTENT_ENCODING = "Content-Encoding"; + } + + interface ContentType { + /** + * for request,response content type + */ + String TEXT_PLAIN = "text/plain"; + String APPLICATION_X_DOWNLOAD = "application/x-download"; + String APPLICATION_ANDROID_PACKAGE = "application/vnd.android.package-archive"; + String MULTIPART_FORM_DATA = "multipart/form-data"; + String APPLICATION_OCTET_STREAM = "application/octet-stream"; + String BINARY_OCTET_STREAM = "binary/octet-stream"; + String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + } + + interface StatusCode { + + int CONTINUE = 100; + int SWITCHING_PROTOCOLS = 101; + int PROCESSING = 102; + + int OK = 200; + int CREATED = 201; + int ACCEPTED = 202; + int NON_AUTHORITATIVE_INFORMATION = 203; + int NO_CONTENT = 204; + int RESET_CONTENT = 205; + int PARTIAL_CONTENT = 206; + int MULTI_STATUS = 207; + + int MULTIPLE_CHOICES = 300; + int MOVED_PERMANENTLY = 301; + int FOUND = 302; + int SEE_OTHER = 303; + int NOT_MODIFIED = 304; + int USE_PROXY = 305; + int SWITCH_PROXY = 306; + int TEMPORARY_REDIRECT = 307; + + int BAD_REQUEST = 400; + int UNAUTHORIZED = 401; + int PAYMENT_REQUIRED = 402; + int FORBIDDEN = 403; + int NOT_FOUND = 404; + int METHOD_NOT_ALLOWED = 405; + int NOT_ACCEPTABLE = 406; + int REQUEST_TIMEOUT = 408; + int CONFLICT = 409; + int GONE = 410; + int LENGTH_REQUIRED = 411; + int PRECONDITION_FAILED = 412; + int REQUEST_URI_TOO_LONG = 414; + int EXPECTATION_FAILED = 417; + int TOO_MANY_CONNECTIONS = 421; + int UNPROCESSABLE_ENTITY = 422; + int LOCKED = 423; + int FAILED_DEPENDENCY = 424; + int UNORDERED_COLLECTION = 425; + int UPGRADE_REQUIRED = 426; + int RETRY_WITH = 449; + + int INTERNAL_SERVER_ERROR = 500; + int NOT_IMPLEMENTED = 501; + int BAD_GATEWAY = 502; + int SERVICE_UNAVAILABLE = 503; + int GATEWAY_TIMEOUT = 504; + int HTTP_VERSION_NOT_SUPPORTED = 505; + int VARIANT_ALSO_NEGOTIATES = 506; + int INSUFFICIENT_STORAGE = 507; + int LOOP_DETECTED = 508; + int BANDWIDTH_LIMIT_EXCEEDED = 509; + int NOT_EXTENDED = 510; + int UNPARSEABLE_RESPONSE_HEADERS = 600; + } + } + + interface RequestScope { + String SESSION = "session"; + } + + interface RequestParameter { + String RETURN_URL = "returnUrl"; + } + + interface Database { + String COLUMN_NAME_TOTAL = "TOTAL"; + + interface MySql { + /** + * pagination + */ + String PAGINATION = "LIMIT"; + } + } + + interface Capacity { + /** + * bytes per kilobytes + */ + int BYTES_PER_KB = 1024; + + /** + * bytes per millionbytes + */ + int BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB; + } + + interface Method { + String PREFIX_SET = "set"; + String PREFIX_GET = "get"; + String PREFIX_IS = "is"; + String GET_CLASS = "getClass"; + } + + interface File { + String CLASS = "class"; + String JPEG = "jpeg"; + String JPG = "jpg"; + String GIF = "gif"; + String JAR = "jar"; + String JAVA = "java"; + String EXE = "exe"; + String DEX = "dex"; + String AIDL = "aidl"; + String SO = "so"; + String XML = "xml"; + String CSV = "csv"; + String TXT = "txt"; + String APK = "apk"; + } + + interface Protocol { + String FILE = "file://"; + String HTTP = "http://"; + String FTP = "ftp://"; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/DefaultFileCopyProcessor.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/DefaultFileCopyProcessor.java new file mode 100644 index 00000000..f20d4682 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/DefaultFileCopyProcessor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +public class DefaultFileCopyProcessor implements FileUtil.FileCopyProcessor { + + /** + * copyFileToFileProcess + * + * @param from,maybe directory + * @param to,maybe directory + * @param isFile,maybe directory or file + * @return boolean, if true keep going copy,only active in directory so far + */ + public boolean copyFileToFileProcess(final String from, final String to, final boolean isFile) { + try { + if (isFile) { + String fromFile = new File(from).getAbsolutePath(); + String toFile = new File(to).getAbsolutePath(); + if (fromFile.equals(toFile)) { + toFile = toFile + "_copy"; + } + FileUtil.createFile(toFile); + InputStream inputStream = new FileInputStream(fromFile); + OutputStream outputStream = new FileOutputStream(toFile); + try { + byte[] buffer = new byte[Constant.Capacity.BYTES_PER_KB]; + int length = -1; + while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { + outputStream.write(buffer, 0, length); + outputStream.flush(); + } + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } + } else { + FileUtil.createDirectory(to); + } + } catch (Exception e) { + throw new FileCopyException(e); + } + return true; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FakeRDotTxtEntry.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FakeRDotTxtEntry.java new file mode 100644 index 00000000..b8ac4575 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FakeRDotTxtEntry.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +/** + * An {@link RDotTxtEntry} with fake {@link #idValue}, useful for comparing two resource entries for + * equality, since {@link RDotTxtEntry#compareTo(RDotTxtEntry)} ignores the id value. + */ +public class FakeRDotTxtEntry extends RDotTxtEntry { + + private static final String FAKE_ID = "0x00000000"; + + public FakeRDotTxtEntry(IdType idType, RType type, String name) { + super(idType, type, name, FAKE_ID); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileCopyException.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileCopyException.java new file mode 100644 index 00000000..e7fb8a84 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileCopyException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +public class FileCopyException extends RuntimeException { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6670157031514003361L; + + /** + * @param message + */ + public FileCopyException(String message) { + super(message); + } + + /** + * @param cause + */ + public FileCopyException(Throwable cause) { + super(cause); + } + + /** + * @param message + * @param cause + */ + public FileCopyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileUtil.java new file mode 100644 index 00000000..ad2b5eb1 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileUtil.java @@ -0,0 +1,1286 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public final class FileUtil { + + private static final FileCopyProcessor DEFAULT_FILE_COPY_PROCESSOR = new DefaultFileCopyProcessor(); + + private FileUtil() { + } + + /** + * is file exist,include directory or file + * + * @param path directory or file + * @return boolean + */ + public static boolean isExist(String path) { + File file = new File(path); + return file.exists(); + } + + /** + * is has file from directory + * + * @param directory + * @param fileSuffix + * @return boolean + */ + public static boolean isHasFile(String directory, String fileSuffix) { + boolean result = false; + File directoryFile = new File(directory); + Queue queue = new ConcurrentLinkedQueue(); + queue.add(directoryFile); + while (!queue.isEmpty()) { + File file = queue.poll(); + if (file.isDirectory()) { + File[] fileArray = file.listFiles(); + if (fileArray != null) { + queue.addAll(Arrays.asList(fileArray)); + } + } else if (file.isFile()) { + if (file.getName().toLowerCase().endsWith(fileSuffix.toLowerCase())) { + result = true; + break; + } + } + } + return result; + } + + /** + * create directory + * + * @param directoryPath + */ + public static void createDirectory(final String directoryPath) { + File file = new File(directoryPath); + if (!file.exists()) { + file.setReadable(true, false); + file.setWritable(true, true); + file.mkdirs(); + } + } + + /** + * create file,full filename,signle empty file. + * + * @param fullFilename + * @return boolean + */ + public static boolean createFile(final String fullFilename) { + boolean result = false; + File file = new File(fullFilename); + createDirectory(file.getParent()); + try { + file.setReadable(true, false); + file.setWritable(true, true); + result = file.createNewFile(); + } catch (Exception e) { + throw new FileUtilException(e); + } + return result; + } + + /** + * delete all file + * + * @param directory + */ + public static void deleteAllFile(String directory) { + List fileList = new ArrayList(); + File directoryFile = new File(directory); + Queue queue = new ConcurrentLinkedQueue(); + queue.add(directoryFile); + while (!queue.isEmpty()) { + File file = queue.poll(); + if (file.isDirectory()) { + File[] fileArray = file.listFiles(); + if (fileArray != null) { + queue.addAll(Arrays.asList(fileArray)); + } + } + fileList.add(file); + } + for (int i = fileList.size() - 1; i >= 0; i--) { + fileList.get(i).delete(); + } + } + + /** + * copy file,default path to path + * + * @param from + * @param to + */ + public static void copyFile(final String from, final String to) { + copyFile(from, to, FileCopyType.PATH_TO_PATH, DEFAULT_FILE_COPY_PROCESSOR); + } + + /** + * copy file + * + * @param from + * @param to + * @param fileCopyType + */ + public static void copyFile(final String from, final String to, final FileCopyType fileCopyType) { + copyFile(from, to, fileCopyType, DEFAULT_FILE_COPY_PROCESSOR); + } + + /** + * copy file + * + * @param from + * @param to + * @param fileCopyType + * @param fileCopyProcessor + */ + public static void copyFile(final String from, final String to, final FileCopyType fileCopyType, FileCopyProcessor fileCopyProcessor) { + switch (fileCopyType) { + case FILE_TO_PATH: + copyFileToPath(from, to, fileCopyProcessor); + break; + case FILE_TO_FILE: + copyFileToFile(from, to, fileCopyProcessor); + break; + case PATH_TO_PATH: + default: + copyPathToPath(from, to, fileCopyProcessor); + break; + } + } + + /** + * copy path to path,copy process include directory copy + * + * @param fromPath + * @param toPath + * @param fileCopyProcessor + */ + public static void copyPathToPath(final String fromPath, final String toPath, FileCopyProcessor fileCopyProcessor) { + File fromDirectoryFile = new File(fromPath); + File toDirectoryFile = new File(toPath); + String fromDirectoryPath = fromDirectoryFile.getAbsolutePath(); + String toDirectoryPath = toDirectoryFile.getAbsolutePath(); + if (fromDirectoryPath.equals(toDirectoryPath)) { + toDirectoryPath = toDirectoryPath + "_copy"; + } + Queue queue = new ConcurrentLinkedQueue(); + queue.add(fromDirectoryFile); + while (!queue.isEmpty()) { + File file = queue.poll(); + String fromFilePath = file.getAbsolutePath(); + String toFilePath = toDirectoryPath + fromFilePath.substring(fromDirectoryPath.length()); + if (file.isDirectory()) { + boolean result = true; + if (fileCopyProcessor != null) { + result = fileCopyProcessor.copyFileToFileProcess(fromFilePath, toFilePath, false); + } + if (result) { + File[] fileArray = file.listFiles(); + if (fileArray != null) { + queue.addAll(Arrays.asList(fileArray)); + } + } + } else if (file.isFile()) { + if (fileCopyProcessor != null) { + fileCopyProcessor.copyFileToFileProcess(fromFilePath, toFilePath, true); + } + } + } + } + + /** + * @param fromFile + * @param toPath + * @param fileCopyProcessor + */ + private static void copyFileToPath(final String fromFile, final String toPath, final FileCopyProcessor fileCopyProcessor) { + File from = new File(fromFile); + File to = new File(toPath); + if (from.exists() && from.isFile()) { + createDirectory(toPath); + String tempFromFile = from.getAbsolutePath(); + String tempToFile = to.getAbsolutePath() + File.separator + from.getName(); + copyFileToFile(tempFromFile, tempToFile, fileCopyProcessor); + } + } + + /** + * unzip + * + * @param zipFullFilename + * @param outputDirectory + * @return List + */ + public static List unzip(String zipFullFilename, String outputDirectory) { + return unzip(zipFullFilename, outputDirectory, null); + } + + /** + * unzip + * + * @param zipFullFilename + * @param outputDirectory + * @param zipEntryNameList,if it is null or empty,will unzip all + * @return List + */ + public static List unzip(String zipFullFilename, String outputDirectory, List zipEntryNameList) { + if (outputDirectory == null) { + throw new NullPointerException("out put directory can not be null."); + } + List storeFileList = null; + ZipFile zipFile = null; + try { + storeFileList = new ArrayList(); + zipFile = new ZipFile(zipFullFilename); + String outputDirectoryAbsolutePath = new File(outputDirectory).getAbsolutePath(); + Enumeration enumeration = zipFile.entries(); + while (enumeration.hasMoreElements()) { + ZipEntry zipEntry = enumeration.nextElement(); + String zipEntryName = zipEntry.getName(); + boolean contains = false; + if (zipEntryNameList == null || zipEntryNameList.isEmpty()) { + contains = true; + } else { + if (zipEntryNameList.contains(zipEntryName)) { + contains = true; + } + } + if (contains) { + InputStream inputStream = zipFile.getInputStream(zipEntry); + String outputFullFilename = outputDirectoryAbsolutePath + Constant.Symbol.SLASH_LEFT + zipEntryName; + if (zipEntry.isDirectory()) { + createDirectory(outputFullFilename); + } else { + createFile(outputFullFilename); + OutputStream outputStream = new FileOutputStream(outputFullFilename); + try { + byte[] buffer = new byte[Constant.Capacity.BYTES_PER_KB]; + int length = -1; + while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { + outputStream.write(buffer, 0, length); + outputStream.flush(); + } + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } + storeFileList.add(outputFullFilename); + } + } + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + try { + if (zipFile != null) { + zipFile.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return storeFileList; + } + + /** + * zip + * + * @param outputZipFullFilename + * @param directory + */ + public static void zip(String outputZipFullFilename, String directory) { + zip(outputZipFullFilename, directory, StringUtil.BLANK); + } + + /** + * zip + * + * @param outputZipFullFilename + * @param directory + * @param fileSuffix + */ + public static void zip(String outputZipFullFilename, String directory, String fileSuffix) { + List classFileList = FileUtil.findMatchFile(directory, fileSuffix); + if (classFileList != null && !classFileList.isEmpty()) { + List zipEntryPathList = new ArrayList(); + int classOutputFullFilenameLength = new File(directory).getAbsolutePath().length() + 1; + for (String classFile : classFileList) { + String zipEntryName = classFile.substring(classOutputFullFilenameLength, classFile.length()); + zipEntryName = zipEntryName.replace(Constant.Symbol.SLASH_RIGHT, Constant.Symbol.SLASH_LEFT); + zipEntryPathList.add(new ZipEntryPath(classFile, new ZipEntry(zipEntryName), true)); + } + zip(outputZipFullFilename, zipEntryPathList); + } + } + + /** + * zip + * + * @param outputZipFullFilename + * @param zipEntryPathList + */ + public static void zip(String outputZipFullFilename, List zipEntryPathList) { + zip(outputZipFullFilename, null, zipEntryPathList); + } + + /** + * zip + * + * @param outputZipFullFilename + * @param inputZipFullFilename,can null,the entry will not from the input file + * @param zipEntryPathList + */ + public static void zip(String outputZipFullFilename, String inputZipFullFilename, List zipEntryPathList) { + zip(outputZipFullFilename, inputZipFullFilename, zipEntryPathList, null); + } + + /** + * zip + * + * @param outputZipFullFilename + * @param inputZipFullFilename,can null,the entry will not from the input file + * @param zipProcessor + */ + public static void zip(String outputZipFullFilename, String inputZipFullFilename, ZipProcessor zipProcessor) { + zip(outputZipFullFilename, inputZipFullFilename, null, zipProcessor); + } + + /** + * zip + * + * @param outputZipFullFilename + * @param inputZipFullFilename,can null,the entry will not from the input file + * @param zipEntryPathList + * @param zipProcessor + */ + public static void zip(String outputZipFullFilename, String inputZipFullFilename, List zipEntryPathList, ZipProcessor zipProcessor) { + ZipOutputStream zipOutputStream = null; + ZipFile zipFile = null; + Map zipEntryPathMap = new HashMap(); + List needToAddEntryNameList = new CopyOnWriteArrayList(); + if (zipEntryPathList != null) { + for (ZipEntryPath zipEntryPath : zipEntryPathList) { + zipEntryPathMap.put(zipEntryPath.zipEntry.getName(), zipEntryPath); + needToAddEntryNameList.add(zipEntryPath.zipEntry.getName()); + } + } + try { + createFile(outputZipFullFilename); + zipOutputStream = new ZipOutputStream(new FileOutputStream(outputZipFullFilename)); + if (inputZipFullFilename != null) { + zipFile = new ZipFile(inputZipFullFilename); + Enumeration enumeration = zipFile.entries(); + while (enumeration.hasMoreElements()) { + ZipEntry zipEntry = enumeration.nextElement(); + String zipEntryName = zipEntry.getName(); + InputStream inputStream = null; + if (zipEntryPathMap.containsKey(zipEntryName)) { + ZipEntryPath zipEntryPath = zipEntryPathMap.get(zipEntryName); + needToAddEntryNameList.remove(zipEntryName); + if (zipEntryPath.replace) { + zipEntry = zipEntryPath.zipEntry; + inputStream = new FileInputStream(zipEntryPath.fullFilename); + } + } + if (inputStream == null) { + inputStream = zipFile.getInputStream(zipEntry); + if (zipProcessor != null) { + inputStream = zipProcessor.zipEntryProcess(zipEntryName, inputStream); + } + } + ZipEntry newZipEntry = new ZipEntry(zipEntryName); + addZipEntry(zipOutputStream, newZipEntry, inputStream); + } + } + for (String zipEntryName : needToAddEntryNameList) { + ZipEntryPath zipEntryPath = zipEntryPathMap.get(zipEntryName); + ZipEntry zipEntry = zipEntryPath.zipEntry; + InputStream inputStream = new FileInputStream(zipEntryPath.fullFilename); + if (zipProcessor != null) { + inputStream = zipProcessor.zipEntryProcess(zipEntry.getName(), inputStream); + } + addZipEntry(zipOutputStream, zipEntry, inputStream); + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + try { + if (zipOutputStream != null) { + zipOutputStream.finish(); + zipOutputStream.flush(); + zipOutputStream.close(); + } + if (zipFile != null) { + zipFile.close(); + } + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + + /** + * merge zip file + * + * @param zipOutputFullFilename + * @param mergeZipFullFilenameList + */ + public static void mergeZip(String zipOutputFullFilename, List mergeZipFullFilenameList) { + FileUtil.createFile(zipOutputFullFilename); + ZipOutputStream zipOutputStream = null; + try { + zipOutputStream = new ZipOutputStream(new FileOutputStream(zipOutputFullFilename)); + if (mergeZipFullFilenameList != null) { + for (String zipFullFilename : mergeZipFullFilenameList) { + if (isExist(zipFullFilename)) { + ZipFile zipFile = new ZipFile(zipFullFilename); + Enumeration enumeration = zipFile.entries(); + while (enumeration.hasMoreElements()) { + ZipEntry zipEntry = enumeration.nextElement(); + InputStream inputStream = zipFile.getInputStream(zipEntry); + addZipEntry(zipOutputStream, zipEntry, inputStream); + } + zipFile.close(); + } + } + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + try { + if (zipOutputStream != null) { + zipOutputStream.close(); + } + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + + /** + * add zip entry + * + * @param zipOutputStream + * @param zipEntry + * @param inputStream + * @throws Exception + */ + public static void addZipEntry(ZipOutputStream zipOutputStream, ZipEntry zipEntry, InputStream inputStream) throws Exception { + try { + zipOutputStream.putNextEntry(zipEntry); + byte[] buffer = new byte[Constant.Capacity.BYTES_PER_KB]; + int length = -1; + while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { + zipOutputStream.write(buffer, 0, length); + zipOutputStream.flush(); + } + } catch (ZipException e) { + // do nothing + } finally { + if (inputStream != null) { + inputStream.close(); + } + zipOutputStream.closeEntry(); + } + } + + /** + * read file + * + * @param fullFilename + * @return byte[] + */ + public static byte[] readFile(String fullFilename) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + InputStream inputStream = null; + try { + inputStream = new FileInputStream(fullFilename); + copyStream(inputStream, byteArrayOutputStream); + } catch (FileNotFoundException e) { + throw new FileUtilException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + if (byteArrayOutputStream != null) { + try { + byteArrayOutputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + return byteArrayOutputStream.toByteArray(); + } + + /** + * write file + * + * @param outputFullFilename + * @param byteArray + */ + public static void writeFile(String outputFullFilename, byte[] byteArray) { + InputStream inputStream = new ByteArrayInputStream(byteArray); + FileUtil.createFile(outputFullFilename); + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(outputFullFilename); + copyStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + throw new FileUtilException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + } + + /** + * copy stream , from input to output,it don't close + * + * @param inputStream + * @param outputStream + */ + public static void copyStream(InputStream inputStream, OutputStream outputStream) { + if (inputStream != null && outputStream != null) { + try { + int length = -1; + byte[] buffer = new byte[Constant.Capacity.BYTES_PER_MB]; + while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { + outputStream.write(buffer, 0, length); + outputStream.flush(); + } + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + + /** + * merge file + * + * @param outputFullFilename + * @param fullFilenameList + */ + public static void mergeFile(String outputFullFilename, List fullFilenameList) { + if (fullFilenameList != null && outputFullFilename != null) { + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(outputFullFilename); + for (String fullFilename : fullFilenameList) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(fullFilename); + copyStream(inputStream, outputStream); + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + } + } + + /** + * find match file directory + * + * @param sourceDirectory + * @param fileSuffix + * @return List + */ + public static List findMatchFileDirectory(String sourceDirectory, String fileSuffix) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, null, false, true); + } + + /** + * find match file directory + * + * @param sourceDirectory + * @param fileSuffix + * @param includeHidden + * @return List + */ + public static List findMatchFileDirectory(String sourceDirectory, String fileSuffix, boolean includeHidden) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, null, false, includeHidden); + } + + /** + * find match file directory and append some string to rear + * + * @param sourceDirectory + * @param fileSuffix + * @param somethingAppendToRear + * @return List + */ + public static List findMatchFileDirectory(String sourceDirectory, String fileSuffix, String somethingAppendToRear) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, false, true); + } + + /** + * find match file directory and append some string to rear + * + * @param sourceDirectory + * @param fileSuffix + * @param somethingAppendToRear + * @param includeHidden + * @return List + */ + public static List findMatchFileDirectory(String sourceDirectory, String fileSuffix, String somethingAppendToRear, boolean includeHidden) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, false, includeHidden); + } + + /** + * find match file + * + * @param sourceDirectory + * @param fileSuffix + * @return List + */ + public static List findMatchFile(String sourceDirectory, String fileSuffix) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, null, true, true); + } + + /** + * find match file + * + * @param sourceDirectory + * @param fileSuffix + * @param includeHidden + * @return List + */ + public static List findMatchFile(String sourceDirectory, String fileSuffix, boolean includeHidden) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, null, true, includeHidden); + } + + /** + * find match file and append some string to rear + * + * @param sourceDirectory + * @param fileSuffix + * @param somethingAppendToRear + * @return List + */ + public static List findMatchFile(String sourceDirectory, String fileSuffix, String somethingAppendToRear) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, true, false); + } + + /** + * find match file and append some string to rear + * + * @param sourceDirectory + * @param fileSuffix + * @param somethingAppendToRear + * @param includeHidden + * @return List + */ + public static List findMatchFile(String sourceDirectory, String fileSuffix, String somethingAppendToRear, boolean includeHidden) { + return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, true, includeHidden); + } + + /** + * find match file or match file directory + * + * @param sourceDirectory + * @param fileSuffix + * @param somethingAppendToRear + * @param isFindMatchFile + * @param includeHidden + * @return List + */ + private static List findMatchFileOrMatchFileDirectory(String sourceDirectory, String fileSuffix, String somethingAppendToRear, boolean isFindMatchFile, boolean includeHidden) { + fileSuffix = StringUtil.nullToBlank(fileSuffix); + somethingAppendToRear = StringUtil.nullToBlank(somethingAppendToRear); + List list = new ArrayList(); + File sourceDirectoryFile = new File(sourceDirectory); + Queue queue = new ConcurrentLinkedQueue(); + queue.add(sourceDirectoryFile); + while (!queue.isEmpty()) { + File file = queue.poll(); + boolean result = false; + if (!file.isHidden() || includeHidden) { + result = true; + } + if (result) { + if (file.isDirectory()) { + File[] fileArray = file.listFiles(); + if (fileArray != null) { + queue.addAll(Arrays.asList(fileArray)); + } + } else if (file.isFile()) { + if (file.getName().toLowerCase().endsWith(fileSuffix.toLowerCase())) { + if (isFindMatchFile) { + list.add(file.getAbsolutePath() + somethingAppendToRear); + } else { + String parentPath = file.getParent(); + parentPath = parentPath + somethingAppendToRear; + if (!list.contains(parentPath)) { + list.add(parentPath); + } + } + } + } + } + } + return list; + } + + /** + * get zip entry hash map + * + * @param zipFile + * @return Map + */ + private static Map getZipEntryHashMap(String zipFullFilename) { + ZipFile zipFile = null; + Map map = new HashMap(); + try { + zipFile = new ZipFile(zipFullFilename); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = (ZipEntry) entries.nextElement(); + if (!zipEntry.isDirectory()) { + String key = zipEntry.getName(); + String value = zipEntry.getCrc() + Constant.Symbol.DOT + zipEntry.getSize(); + map.put(key, value); + } + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + return map; + } + + /** + * differ zip + * + * @param differentOutputFullFilename + * @param oldZipFullFilename + * @param newZipFullFilename + */ + public static void differZip(String differentOutputFullFilename, String oldZipFullFilename, String newZipFullFilename) { + Map map = getZipEntryHashMap(oldZipFullFilename); + ZipFile newZipFile = null; + ZipOutputStream zipOutputStream = null; + try { + newZipFile = new ZipFile(newZipFullFilename); + Enumeration entries = newZipFile.entries(); + FileUtil.createFile(differentOutputFullFilename); + zipOutputStream = new ZipOutputStream(new FileOutputStream(differentOutputFullFilename)); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + if (!zipEntry.isDirectory()) { + String zipEntryName = zipEntry.getName(); + String oldZipEntryHash = map.get(zipEntryName); + String newZipEntryHash = zipEntry.getCrc() + Constant.Symbol.DOT + zipEntry.getSize(); + // old zip entry hash not exist is a new zip entry,if exist + // is a modified zip entry + if (oldZipEntryHash == null || (!newZipEntryHash.equals(oldZipEntryHash))) { + System.out.println(String.format("found modified entry, key=%s(%s/%s)", new Object[]{zipEntryName, oldZipEntryHash, newZipEntryHash})); + addZipEntry(zipOutputStream, zipEntry, newZipFile.getInputStream(zipEntry)); + } + } + } + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (newZipFile != null) { + try { + newZipFile.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + if (zipOutputStream != null) { + try { + zipOutputStream.finish(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + } + + /** + * generate simple file + * + * @param templateFullFilename + * @param outputFullFilename + * @param valueMap + */ + public static void generateSimpleFile(String templateFullFilename, String outputFullFilename, Map valueMap) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(templateFullFilename); + generateSimpleFile(inputStream, outputFullFilename, valueMap); + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + } + } + + /** + * generate simple file + * + * @param templateInputStream + * @param outputFullFilename + * @param valueMap + */ + public static void generateSimpleFile(InputStream templateInputStream, String outputFullFilename, Map valueMap) { + BufferedReader bufferedReader = null; + OutputStream outputStream = null; + try { + bufferedReader = new BufferedReader(new InputStreamReader(templateInputStream, Constant.Encoding.UTF8)); + StringBuilder content = new StringBuilder(); + String line = null; + Set> entrySet = valueMap.entrySet(); + while ((line = bufferedReader.readLine()) != null) { + for (Entry entry : entrySet) { + String key = entry.getKey(); + String value = entry.getValue(); + line = line.replace(key, value); + } + content.append(line); + content.append(StringUtil.CRLF_STRING); + } + createFile(outputFullFilename); + outputStream = new FileOutputStream(outputFullFilename); + outputStream.write(content.toString().getBytes(Constant.Encoding.UTF8)); + outputStream.flush(); + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + throw new FileUtilException(e); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + } + + /** + * find file list with cache + * + * @param sourceDirectoryList + * @param cacheProperties + * @param fileSuffix suffix it will search file in source directory list + * @param somethingAppendToRear + * @param isFile if true the return list is source file else is the source directory + * @return List + */ + public static List findFileListWithCache(List sourceDirectoryList, Properties cacheProperties, String fileSuffix, String somethingAppendToRear, boolean isFile) { + return findFileListWithCache(sourceDirectoryList, cacheProperties, fileSuffix, somethingAppendToRear, isFile, null); + } + + /** + * find file list with cache + * + * @param sourceDirectoryList + * @param cacheProperties + * @param fileSuffix + * @param somethingAppendToRear + * @param isFile + * @param cacheProcessor + * @return List + */ + public static List findFileListWithCache(List sourceDirectoryList, Properties cacheProperties, String fileSuffix, String somethingAppendToRear, boolean isFile, CacheProcessor cacheProcessor) { + return findFileListWithCache(sourceDirectoryList, cacheProperties, fileSuffix, somethingAppendToRear, isFile, false, cacheProcessor); + } + + /** + * find file list with cache + * + * @param sourceDirectoryList + * @param cacheProperties + * @param fileSuffix suffix it will search file in source directory list + * @param somethingAppendToRear + * @param isFile if true the return list is source file else is the source directory + * @param includeHidden + * @return List + */ + public static List findFileListWithCache(List sourceDirectoryList, Properties cacheProperties, String fileSuffix, String somethingAppendToRear, boolean isFile, boolean includeHidden, CacheProcessor cacheProcessor) { + List sourceList = new ArrayList(); + //no cache + if (cacheProperties == null) { + if (sourceDirectoryList != null && !sourceDirectoryList.isEmpty()) { + for (String sourceDirectory : sourceDirectoryList) { + if (isFile) { + sourceList.addAll(FileUtil.findMatchFile(sourceDirectory, fileSuffix, includeHidden)); + } else { + sourceList.addAll(FileUtil.findMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, includeHidden)); + } + } + } + } else if (cacheProperties.isEmpty()) { + List fileList = new ArrayList(); + if (sourceDirectoryList != null && !sourceDirectoryList.isEmpty()) { + for (String sourceDirectory : sourceDirectoryList) { + fileList.addAll(FileUtil.findMatchFile(sourceDirectory, fileSuffix, includeHidden)); + } + } + for (String fullFilename : fileList) { + String cacheKey = fullFilename; + if (cacheProcessor != null) { + cacheKey = cacheProcessor.keyProcess(cacheKey); + } + cacheProperties.setProperty(cacheKey, Generator.md5File(fullFilename)); + } + if (isFile) { + sourceList.addAll(fileList); + } else { + if (sourceDirectoryList != null && !sourceDirectoryList.isEmpty()) { + for (String sourceDirectory : sourceDirectoryList) { + sourceList.addAll(FileUtil.findMatchFileDirectory(sourceDirectory, fileSuffix, somethingAppendToRear, includeHidden)); + } + } + } + } else { + List fileList = new ArrayList(); + if (sourceDirectoryList != null && !sourceDirectoryList.isEmpty()) { + for (String sourceDirectory : sourceDirectoryList) { + fileList.addAll(FileUtil.findMatchFile(sourceDirectory, fileSuffix, includeHidden)); + } + } + for (String fullFilename : fileList) { + String cacheKey = fullFilename; + if (cacheProcessor != null) { + cacheKey = cacheProcessor.keyProcess(cacheKey); + } + String sourceFileMd5 = Generator.md5File(fullFilename); + if (cacheProperties.containsKey(cacheKey)) { + String md5 = cacheProperties.getProperty(cacheKey); + if (!sourceFileMd5.equals(md5)) { + sourceList.add(fullFilename); + cacheProperties.setProperty(cacheKey, sourceFileMd5); + } + } else { + sourceList.add(fullFilename); + cacheProperties.setProperty(cacheKey, sourceFileMd5); + } + } + } + return sourceList; + } + + /** + * deal with file cache + * + * @param propertiesFileMappingFullFilename + * @param noCacheFileFinder + * @param noCacheFileProcessor + * @return List + */ + public static List dealWithFileCache(String propertiesFileMappingFullFilename, NoCacheFileFinder noCacheFileFinder, NoCacheFileProcessor noCacheFileProcessor) { + Properties propertiesFileMapping = getPropertiesAutoCreate(propertiesFileMappingFullFilename); + List noCacheFileList = null; + if (noCacheFileFinder == null) { + throw new NullPointerException("noCacheFileFinder can not be null."); + } + noCacheFileList = noCacheFileFinder.findNoCacheFileList(propertiesFileMapping); + boolean saveCache = false; + if (noCacheFileProcessor != null) { + saveCache = noCacheFileProcessor.process(noCacheFileList); + } + if (saveCache) { + saveProperties(propertiesFileMapping, propertiesFileMappingFullFilename); + } + return noCacheFileList; + } + + /** + * get properties will auto create + * + * @param propertiesFullFilename + * @return Properties + */ + public static Properties getPropertiesAutoCreate(String propertiesFullFilename) { + if (!FileUtil.isExist(propertiesFullFilename)) { + FileUtil.createFile(propertiesFullFilename); + } + return getProperties(propertiesFullFilename); + } + + /** + * get properties + * + * @param propertiesFullFilename + * @return Properties + */ + public static Properties getProperties(String propertiesFullFilename) { + Properties properties = null; + if (propertiesFullFilename != null) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(propertiesFullFilename); + properties = new Properties(); + properties.load(inputStream); + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + } + return properties; + } + + /** + * get properties from properties file,will auto create + * + * @param file + * @return Properties + * @throws IOException + */ + public static Properties getProperties(File file) { + Properties properties = null; + if (file != null) { + properties = getProperties(file.getAbsolutePath()); + } + return properties; + } + + /** + * save properties + * + * @param properties + * @param outputFullFilename + */ + public static void saveProperties(Properties properties, String outputFullFilename) { + if (properties != null && outputFullFilename != null) { + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(outputFullFilename); + properties.store(outputStream, null); + } catch (Exception e) { + throw new FileUtilException(e); + } finally { + if (outputStream != null) { + try { + outputStream.flush(); + outputStream.close(); + } catch (Exception e) { + throw new FileUtilException(e); + } + } + } + } + } + + /** + * @param fromFile + * @param toFile + * @param fileCopyProcessor + */ + private static void copyFileToFile(final String fromFile, final String toFile, FileCopyProcessor fileCopyProcessor) { + if (fileCopyProcessor != null) { + createFile(toFile); + fileCopyProcessor.copyFileToFileProcess(fromFile, toFile, true); + } + } + + /** + * @param args + */ + public static void main(String[] args) { + String outputZipFullFilename = "/D:/a/b.zip"; + mergeZip(outputZipFullFilename, Arrays.asList("/D:/a.zip", "/D:/b.zip")); + } + + public enum FileCopyType { + PATH_TO_PATH, FILE_TO_PATH, FILE_TO_FILE + } + + public interface FileCopyProcessor { + + /** + * copyFileToFileProcess + * + * @param from,maybe directory + * @param to,maybe directory + * @param isFile,maybe directory or file + * @return boolean, if true keep going copy,only active in directory so + * far + */ + boolean copyFileToFileProcess(final String from, final String to, final boolean isFile); + + } + + public interface ZipProcessor { + + /** + * zip entry process + * + * @param zipEntryName + * @param inputStream + * @return InputStream + */ + InputStream zipEntryProcess(final String zipEntryName, InputStream inputStream); + } + + public interface CacheProcessor { + /** + * key process,can change key to save cache + * + * @param cacheKey + * @return String + */ + String keyProcess(final String key); + } + + public interface NoCacheFileProcessor { + /** + * process + * + * @param uncachedFileList + * @return boolean, true is save cache else false + */ + boolean process(List uncachedFileList); + } + + + public interface NoCacheFileFinder { + + /** + * find no cache file list + * + * @param cacheFileMapping + * @return List + */ + List findNoCacheFileList(Properties cacheFileMapping); + } + + public static class ZipEntryPath { + private String fullFilename = null; + private ZipEntry zipEntry = null; + private boolean replace = false; + + public ZipEntryPath(String fullFilename, ZipEntry zipEntry) { + this(fullFilename, zipEntry, false); + } + + public ZipEntryPath(String fullFilename, ZipEntry zipEntry, boolean replace) { + this.fullFilename = fullFilename; + this.zipEntry = zipEntry; + this.replace = replace; + } + } + + public static class FileUtilException extends RuntimeException { + private static final long serialVersionUID = 3884649425767533205L; + + public FileUtilException(Throwable cause) { + super(cause); + } + } + +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Generator.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Generator.java new file mode 100644 index 00000000..452349e3 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Generator.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; + +public final class Generator { + + private static final char[] characters = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + private static final String FONT_FAMILY_TIMES_NEW_ROMAN = "Times New Roman"; + + /** + * md5 file + * + * @param fullFilename + * @return String + */ + public static String md5File(String fullFilename) { + String result = null; + if (fullFilename != null) { + try { + result = md5File(new FileInputStream(fullFilename)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return result; + } + + /** + * md5 file + * + * @param inputStream + * @return String + */ + public static String md5File(final InputStream inputStream) { + String result = null; + if (inputStream != null) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[Constant.Capacity.BYTES_PER_KB]; + int readCount = 0; + while ((readCount = inputStream.read(buffer, 0, buffer.length)) != -1) { + md.update(buffer, 0, readCount); + } + result = StringUtil.byteToHexString(md.digest()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return result; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/JavaXmlUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/JavaXmlUtil.java new file mode 100644 index 00000000..c6400b75 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/JavaXmlUtil.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import org.w3c.dom.Document; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +public final class JavaXmlUtil { + + /** + * get document builder + * + * @return DocumentBuilder + */ + private static DocumentBuilder getDocumentBuilder() { + DocumentBuilder documentBuilder = null; + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } + return documentBuilder; + } + + public static Document getEmptyDocument() { + Document document = null; + try { + DocumentBuilder documentBuilder = getDocumentBuilder(); + document = documentBuilder.newDocument(); + document.normalize(); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } + return document; + } + + /** + * parse + * + * @param filename + * @return Document + */ + public static Document parse(final String filename) { + Document document = null; + try { + DocumentBuilder documentBuilder = getDocumentBuilder(); + document = documentBuilder.parse(new File(filename)); + document.normalize(); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } + return document; + } + + /** + * parse + * + * @param inputStream + * @return Document + */ + public static Document parse(final InputStream inputStream) { + Document document = null; + try { + DocumentBuilder documentBuilder = getDocumentBuilder(); + document = documentBuilder.parse(inputStream); + document.normalize(); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } + return document; + } + + /** + * save document + * + * @param document + * @param outputFullFilename + */ + public static void saveDocument(final Document document, final String outputFullFilename) { + OutputStream outputStream = null; + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource domSource = new DOMSource(document); + transformer.setOutputProperty(OutputKeys.ENCODING, Constant.Encoding.UTF8); + outputStream = new FileOutputStream(outputFullFilename); + StreamResult result = new StreamResult(outputStream); + transformer.transform(domSource, result); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (Exception e) { + throw new JavaXmlUtilException(e); + } + } + } + } + + public static class JavaXmlUtilException extends RuntimeException { + private static final long serialVersionUID = 4669527982017700891L; + + public JavaXmlUtilException(Throwable cause) { + super(cause); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ObjectUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ObjectUtil.java new file mode 100644 index 00000000..ef7664a8 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ObjectUtil.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +/** + * reflect the object property and invoke the method + * + * @author Dandelion + * @since 2008-04-?? + */ +public final class ObjectUtil { + + private ObjectUtil() { + } + + /** + * when object is null return blank,when the object is not null it return object; + * + * @param object + * @return Object + */ + public static Object nullToBlank(Object object) { + if (object == null) { + return StringUtil.BLANK; + } + return object; + } + + /** + * equal + * + * @param a + * @param b + * @return boolean + */ + public static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * field name to method name + * + * @param methodPrefix + * @param fieldName + * @return methodName + */ + public static String fieldNameToMethodName(String methodPrefix, String fieldName) { + return fieldNameToMethodName(methodPrefix, fieldName, false); + } + + /** + * field name to method name + * + * @param methodPrefix + * @param fieldName + * @param ignoreFirstLetterCase + * @return methodName + */ + public static String fieldNameToMethodName(String methodPrefix, String fieldName, boolean ignoreFirstLetterCase) { + String methodName = null; + if (fieldName != null && fieldName.length() > 0) { + if (ignoreFirstLetterCase) { + methodName = methodPrefix + fieldName; + } else { + methodName = methodPrefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); + } + } else { + methodName = methodPrefix; + } + return methodName; + } + + /** + * method name to field name + * + * @param methodPrefix + * @param methodName + * @return fieldName + */ + public static String methodNameToFieldName(String methodPrefix, String methodName) { + return methodNameToFieldName(methodPrefix, methodName, false); + } + + /** + * method name to field name + * + * @param methodPrefix + * @param methodName + * @param ignoreFirstLetterCase + * @return fieldName + */ + public static String methodNameToFieldName(String methodPrefix, String methodName, boolean ignoreFirstLetterCase) { + String fieldName = null; + if (methodName != null && methodName.length() > methodPrefix.length()) { + int front = methodPrefix.length(); + if (ignoreFirstLetterCase) { + fieldName = methodName.substring(front, front + 1) + methodName.substring(front + 1); + } else { + fieldName = methodName.substring(front, front + 1).toLowerCase() + methodName.substring(front + 1); + } + } + return fieldName; + } +} \ No newline at end of file diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/PatchUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/PatchUtil.java new file mode 100644 index 00000000..87f829f2 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/PatchUtil.java @@ -0,0 +1,198 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; +import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class PatchUtil { + + /** + * read r txt + * + * @param rTxtFullFilename + * @return Map> + */ + public static Map> readRTxt(String rTxtFullFilename) { + //read base resource entry + Map> rTypeResourceMap = new HashMap>(); + if (StringUtil.isNotBlank(rTxtFullFilename) && FileUtil.isExist(rTxtFullFilename)) { + BufferedReader bufferedReader = null; + try { + final Pattern textSymbolLine = Pattern.compile("(\\S+) (\\S+) (\\S+) (.+)"); + bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(rTxtFullFilename))); + String line = null; + while ((line = bufferedReader.readLine()) != null) { + Matcher matcher = textSymbolLine.matcher(line); + if (matcher.matches()) { + IdType idType = IdType.from(matcher.group(1)); + RType rType = RType.valueOf(matcher.group(2).toUpperCase()); + String name = matcher.group(3); + String idValue = matcher.group(4); + RDotTxtEntry rDotTxtEntry = new RDotTxtEntry(idType, rType, name, idValue); + Set hashSet = null; + if (rTypeResourceMap.containsKey(rType)) { + hashSet = rTypeResourceMap.get(rType); + } else { + hashSet = new HashSet(); + rTypeResourceMap.put(rType, hashSet); + } + hashSet.add(rDotTxtEntry); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + return rTypeResourceMap; + } + + /** + * generate public resource xml + * + * @param aaptResourceCollector + * @param outputIdsXmlFullFilename + * @param outputPublicXmlFullFilename + */ + public static void generatePublicResourceXml(AaptResourceCollector aaptResourceCollector, String outputIdsXmlFullFilename, String outputPublicXmlFullFilename) { + if (aaptResourceCollector == null) { + return; + } + FileUtil.createFile(outputIdsXmlFullFilename); + FileUtil.createFile(outputPublicXmlFullFilename); + PrintWriter idsWriter = null; + PrintWriter publicWriter = null; + try { + FileUtil.createFile(outputIdsXmlFullFilename); + FileUtil.createFile(outputPublicXmlFullFilename); + idsWriter = new PrintWriter(new FileOutputStream(outputIdsXmlFullFilename)); + publicWriter = new PrintWriter(new FileOutputStream(outputPublicXmlFullFilename)); + idsWriter.println(""); + publicWriter.println(""); + idsWriter.println(""); + publicWriter.println(""); + Map> map = aaptResourceCollector.getRTypeResourceMap(); + Iterator>> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + RType rType = entry.getKey(); + if (!rType.equals(RType.STYLEABLE)) { + Set set = entry.getValue(); + for (RDotTxtEntry rDotTxtEntry : set) { + if (rType.equals(RType.STYLE)) { + String rawName = aaptResourceCollector.getRawName(rDotTxtEntry.name); + if (StringUtil.isBlank(rawName)) { + System.err.println("Blank?" + rDotTxtEntry.name); + rawName = rDotTxtEntry.name; + } + publicWriter.println(""); + } else { + publicWriter.println(""); + } + } + Set ignoreIdSet = aaptResourceCollector.getIgnoreIdSet(); + for (RDotTxtEntry rDotTxtEntry : set) { + if (rType.equals(RType.ID) && !ignoreIdSet.contains(rDotTxtEntry.name)) { + idsWriter.println(""); + } else if (rType.equals(RType.STYLE)) { + + if (rDotTxtEntry.name.indexOf(Constant.Symbol.UNDERLINE) > 0) { +//idsWriter.println(""); + } + } + } + } + idsWriter.flush(); + publicWriter.flush(); + } + idsWriter.println(""); + publicWriter.println(""); + } catch (Exception e) { + throw new PatchUtilException(e); + } finally { + if (idsWriter != null) { + idsWriter.flush(); + idsWriter.close(); + } + if (publicWriter != null) { + publicWriter.flush(); + publicWriter.close(); + } + } + } + + public static class PublicResourceEntry { + private RType rType = null; + private String resourceName = null; + + public PublicResourceEntry(RType rType, String resourceName) { + this.rType = rType; + this.resourceName = resourceName; + } + + public boolean equals(Object obj) { + if (!(obj instanceof PublicResourceEntry)) { + return false; + } + PublicResourceEntry that = (PublicResourceEntry) obj; + return ObjectUtil.equal(this.rType, that.rType) && ObjectUtil.equal(this.resourceName, that.resourceName); + } + + public int hashCode() { + return Arrays.hashCode(new Object[]{this.rType, this.resourceName}); + } + } + + public static class PatchUtilException extends RuntimeException { + private static final long serialVersionUID = 5982003304074821184L; + + public PatchUtilException(String message) { + super(message); + } + + public PatchUtilException(Throwable cause) { + super(cause); + } + + public PatchUtilException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/RDotTxtEntry.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/RDotTxtEntry.java new file mode 100644 index 00000000..11e43aa8 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/RDotTxtEntry.java @@ -0,0 +1,146 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ComparisonChain; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a row from a symbols file generated by {@code aapt}. + */ +public class RDotTxtEntry implements Comparable { + + private static final Pattern TEXT_SYMBOLS_LINE = Pattern.compile("(\\S+) (\\S+) (\\S+) (.+)"); + public static final Function TO_ENTRY = new Function() { + public RDotTxtEntry apply(String input) { + Optional entry = parse(input); + Preconditions.checkNotNull(entry.isPresent(), "Could not parse R.txt entry: '%s'", input); + return entry.get(); + } + }; + // A symbols file may look like: + // + // int id placeholder 0x7f020000 + // int string debug_http_proxy_dialog_title 0x7f030004 + // int string debug_http_proxy_hint 0x7f030005 + // int string debug_http_proxy_summary 0x7f030003 + // int string debug_http_proxy_title 0x7f030002 + // int string debug_ssl_cert_check_summary 0x7f030001 + // int string debug_ssl_cert_check_title 0x7f030000 + // + // Note that there are four columns of information: + // - the type of the resource id (always seems to be int or int[], in + // practice) + // - the type of the resource + // - the name of the resource + // - the value of the resource id + public final IdType idType; + public final RType type; + public final String name; + public String idValue; + public RDotTxtEntry(IdType idType, RType type, String name, String idValue) { + this.idType = Preconditions.checkNotNull(idType); + this.type = Preconditions.checkNotNull(type); + this.name = Preconditions.checkNotNull(name); + this.idValue = Preconditions.checkNotNull(idValue); + } + + public static Optional parse(String rDotTxtLine) { + Matcher matcher = TEXT_SYMBOLS_LINE.matcher(rDotTxtLine); + if (!matcher.matches()) { + return Optional.absent(); + } + + IdType idType = IdType.from(matcher.group(1)); + RType type = RType.valueOf(matcher.group(2).toUpperCase()); + String name = matcher.group(3); + String idValue = matcher.group(4); + + return Optional.of(new RDotTxtEntry(idType, type, name, idValue)); + } + + public RDotTxtEntry copyWithNewIdValue(String newIdValue) { + return new RDotTxtEntry(idType, type, name, newIdValue); + } + + /** + * A collection of Resources should be sorted such that Resources of the + * same type should be grouped together, and should be alphabetized within + * that group. + */ + public int compareTo(RDotTxtEntry that) { + return ComparisonChain.start().compare(this.type, that.type).compare(this.name, that.name).result(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RDotTxtEntry)) { + return false; + } + + RDotTxtEntry that = (RDotTxtEntry) obj; + return Objects.equal(this.type, that.type) && Objects.equal(this.name, that.name); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{type, name}); + } + + @Override + public String toString() { + return Objects.toStringHelper(RDotTxtEntry.class).add("idType", idType).add("type", type).add("name", name).add("idValue", idValue).toString(); + } + + // Taken from http://developer.android.com/reference/android/R.html + // TRANSITION for api level 19 + public enum RType { + ANIM, ANIMATOR, ARRAY, ATTR, BOOL, COLOR, DIMEN, DRAWABLE, FRACTION, ID, INTEGER, INTERPOLATOR, LAYOUT, MENU, MIPMAP, PLURALS, RAW, STRING, STYLE, STYLEABLE, TRANSITION, XML; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + } + + public enum IdType { + INT, INT_ARRAY; + + public static IdType from(String raw) { + if (raw.equals("int")) { + return INT; + } else if (raw.equals("int[]")) { + return INT_ARRAY; + } + throw new IllegalArgumentException(String.format("'%s' is not a valid ID type.", raw)); + } + + public String toString() { + if (this.equals(INT)) { + return "int"; + } + return "int[]"; + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceDirectory.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceDirectory.java new file mode 100644 index 00000000..246ea2c1 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceDirectory.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class ResourceDirectory { + + public String directoryName = null; + public String resourceFullFilename = null; + public Set resourceEntrySet = new HashSet(); + + public ResourceDirectory(String directoryName, String resourceFullFilename) { + this.directoryName = directoryName; + this.resourceFullFilename = resourceFullFilename; + } + + public int hashCode() { + return Arrays.hashCode(new Object[]{this.directoryName, this.resourceFullFilename}); + } + + + public boolean equals(Object object) { + if (!(object instanceof ResourceDirectory)) { + return false; + } + ResourceDirectory that = (ResourceDirectory) object; + return ObjectUtil.equal(this.directoryName, that.directoryName) && ObjectUtil.equal(this.resourceFullFilename, that.resourceFullFilename); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceEntry.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceEntry.java new file mode 100644 index 00000000..9977be41 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.util.Arrays; + +public class ResourceEntry { + + public String name = null; + public String value = null; + + public ResourceEntry(String name, String value) { + this.name = name; + this.value = value; + } + + public int hashCode() { + return Arrays.hashCode(new Object[]{this.name}); + } + + + public boolean equals(Object object) { + if (!(object instanceof ResourceEntry)) { + return false; + } + ResourceEntry that = (ResourceEntry) object; + return ObjectUtil.equal(this.name, that.name); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/StringUtil.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/StringUtil.java new file mode 100644 index 00000000..2ffab31f --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/StringUtil.java @@ -0,0 +1,328 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.tencent.tinker.build.aapt; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class StringUtil { + + public static final String BLANK = ""; + public static final String SPACE = " "; + public static final String NULL = "null"; + public static final String CRLF_STRING = "\r\n"; + public static final byte CR = '\r'; + public static final byte LF = '\n'; + public static final byte[] CRLF = {CR, LF}; + private static final String METCH_PATTERN_REGEX = "[\\*]+"; + private static final String METCH_PATTERN = Constant.Symbol.WILDCARD; + private static final String METCH_PATTERN_REPLACEMENT = "[\\\\S|\\\\s]*"; + private static final String ZERO = "0"; + + private StringUtil() { + } + + /** + * when string is null return blank,where the string is not null it return string.trim + * + * @param string + * @return String + */ + public static String trim(final String string) { + String result = null; + if (string == null) { + result = BLANK; + } else { + result = string.trim(); + } + return result; + } + + /** + * when string is null return blank string + * + * @param string + * @return String + */ + public static String nullToBlank(final String string) { + return string == null ? BLANK : string; + } + + /** + * when string[] is null return blank array + * + * @param stringArray + * @return String[]{} length==0 + */ + public static String[] nullToBlank(final String[] stringArray) { + String[] result = stringArray; + if (stringArray == null) { + result = new String[]{}; + } + return result; + } + + /** + *

Checks if a String is whitespace, empty ("") or null.

+ *

+ *

+     * StringUtils.isBlank(null)      = true
+     * StringUtils.isBlank("")        = true
+     * StringUtils.isBlank(" ")       = true
+     * StringUtils.isBlank("bob")     = false
+     * StringUtils.isBlank("  bob  ") = false
+     * 
+ * + * @param string the String to check, may be null + * @return true if the String is null, empty or whitespace + */ + public static boolean isBlank(final String string) { + boolean result = false; + int strLen; + if (string == null || (strLen = string.length()) == 0) { + result = true; + } else { + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(string.charAt(i))) { + result = false; + break; + } + } + } + return result; + } + + /** + *

+ * Checks if a String is not empty (""), not null and not whitespace only. + *

+ *

+ *

+     * StringUtils.isNotBlank(null)      = false
+     * StringUtils.isNotBlank("")        = false
+     * StringUtils.isNotBlank(" ")       = false
+     * StringUtils.isNotBlank("bob")     = true
+     * StringUtils.isNotBlank("  bob  ") = true
+     * 
+ * + * @param string the String to check, may be null + * @return true if the String is not empty and not null and + * not whitespace + */ + public static boolean isNotBlank(final String string) { + return !isBlank(string); + } + + /** + * compare stringArray1 and stringArray2 return the different in str1 + * + * @param stringArray1 + * @param stringArray2 + * @return String[] + */ + public static String[] compareString(final String[] stringArray1, final String[] stringArray2) { + String[] differentString = null; + if (stringArray1 != null && stringArray2 != null) { + List list = new ArrayList(); + for (int i = 0; i < stringArray1.length; i++) { + boolean sign = false; + for (int j = 0; j < stringArray2.length; j++) { + if (stringArray1[i].equals(stringArray2[j])) { + sign = true; + break; + } + } + if (!sign) { + list.add(stringArray1[i]); + } + } + differentString = new String[list.size()]; + differentString = list.toArray(differentString); + } + return differentString; + } + + /** + *

Method:only for '*' match pattern,return true of false

+ * + * @param string + * @param patternString + * @return boolean + */ + public static boolean isMatchPattern(final String string, final String patternString) { + boolean result = false; + if (string != null && patternString != null) { + if (patternString.indexOf(METCH_PATTERN) >= 0) { + String matchPattern = Constant.Symbol.XOR + patternString.replaceAll(METCH_PATTERN_REGEX, METCH_PATTERN_REPLACEMENT) + Constant.Symbol.DOLLAR; + result = isMatchRegex(string, matchPattern); + } else { + if (string.equals(patternString)) { + result = true; + } + } + } + return result; + } + + /** + *

Method:only for regex

+ * + * @param string + * @param regex + * @return boolean + */ + public static boolean isMatchRegex(final String string, final String regex) { + boolean result = false; + if (string != null && regex != null) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(string); + result = matcher.find(); + } + return result; + } + + /** + *

Method:only for regex,parse regex group when regex include group

+ * + * @param string + * @param regex + * @return List + */ + public static List parseRegexGroup(final String string, final String regex) { + List groupList = null; + if (string != null && regex != null) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(string); + int groupCount = matcher.groupCount(); + int count = 1; + groupList = new ArrayList(); + if (matcher.find()) { + while (count <= groupCount) { + groupList.add(matcher.group(count)); + count++; + } + } + } + return groupList; + } + + /** + *

+ * Method: check the string match the regex or not and return the match + * field value + * like {xxxx} can find xxxx + *

+ * + * @param string + * @param regex + * @param firstRegex + * @param firstRegexReplace + * @param lastRegexStringLength like {xxxx},last regex string is "}" so last regex string length equals 1 + * @return List + */ + public static List parseStringGroup(final String string, final String regex, final String firstRegex, final String firstRegexReplace, final int lastRegexStringLength) { + List list = null; + if (string != null) { + list = new ArrayList(); + int lastRegexLength = lastRegexStringLength < 0 ? 0 : lastRegexStringLength; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(string); + String group = null; + int start = 0; + while (matcher.find(start)) { + start = matcher.end(); + group = matcher.group(); + group = group.replaceFirst(firstRegex, firstRegexReplace); + group = group.substring(0, group.length() - lastRegexLength); + list.add(group); + } + } + return list; + } + + /** + * byte to hex string + * + * @param byteArray + * @return String + */ + public static String byteToHexString(byte[] byteArray) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < byteArray.length; i++) { + int byteCode = byteArray[i] & 0xFF; + if (byteCode < 0x10) { + builder.append(0); + } + builder.append(Integer.toHexString(byteCode)); + } + return builder.toString(); + } + + /** + * hex string to byte + * + * @param source + * @return byte + */ + public static byte[] hexStringToByte(final String source) { + byte[] bytes = null; + if (source != null) { + bytes = new byte[source.length() / 2]; + int i = 0; + while (i < bytes.length) { + bytes[i] = (byte) (Integer.parseInt(source.substring(i * 2, (i + 1) * 2), 16)); + i++; + } + } + return bytes; + } + + /** + * fill zero + * + * @param length + * @return String + */ + public static String fillZero(int length) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < length; i++) { + stringBuilder.append(ZERO); + } + return stringBuilder.toString(); + } + + /** + *

Method: string mod operator,return 0~(mod-1)

+ * + * @param string + * @param mod + * @return int + */ + public static int stringMod(String string, int mod) { + int hashCode = 0; + if (string != null) { + hashCode = string.hashCode(); + if (hashCode < 0) { + hashCode = Math.abs(hashCode); + hashCode = hashCode < 0 ? 0 : hashCode; + } + } + return hashCode % (mod > 0 ? mod : 1); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/apkparser/AndroidManifest.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/apkparser/AndroidManifest.java new file mode 100644 index 00000000..ea63940f --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/apkparser/AndroidManifest.java @@ -0,0 +1,142 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.apkparser; + +import net.dongliu.apk.parser.ApkParser; +import net.dongliu.apk.parser.bean.ApkMeta; +import net.dongliu.apk.parser.exception.ParserException; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * Created by zhangshaowen on 16/5/5. + */ +public class AndroidManifest { + public static final int TYPE_SERVICE = 1; + public static final int TYPE_ACTIVITY = 2; + public static final int TYPE_BROADCAST_RECEIVER = 3; + public static final int TYPE_CONTENT_PROVIDER = 4; + + public final List activities = new ArrayList<>(); + public final List receivers = new ArrayList<>(); + public final List services = new ArrayList<>(); + public final List providers = new ArrayList<>(); + public final ApkMeta apkMeta; + public final String xml; + + public final HashMap metaDatas = new HashMap<>(); + + + public AndroidManifest(ApkMeta apkMeta, String xml) throws ParserException { + this.apkMeta = apkMeta; + this.xml = xml; + parse(); + } + + public static AndroidManifest getAndroidManifest(File file) throws IOException, ParseException { + ApkParser apkParser = new ApkParser(file); + AndroidManifest androidManifest = new AndroidManifest(apkParser.getApkMeta(), apkParser.getManifestXml()); + return androidManifest; + } + + private static String getAttribute(NamedNodeMap namedNodeMap, String name) { + Node node = namedNodeMap.getNamedItem(name); + if (node == null) { + if (name.startsWith("android:")) { + name = name.substring("android:".length()); + } + node = namedNodeMap.getNamedItem(name); + if (node == null) { + return null; + } + } + return node.getNodeValue(); + } + + /** + * @return a list of all components + */ + public List getComponents() { + List components = new ArrayList<>(); + components.addAll(activities); + components.addAll(services); + components.addAll(receivers); + components.addAll(providers); + return components; + } + + private void parse() throws ParserException { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + Document document; + try { + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + document = builder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))); + Node manifestNode = document.getElementsByTagName("manifest").item(0); + NodeList nodes = manifestNode.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String nodeName = node.getNodeName(); + if (nodeName.equals("application")) { + NodeList children = node.getChildNodes(); + for (int j = 0; j < children.getLength(); j++) { + Node child = children.item(j); + String childName = child.getNodeName(); + switch (childName) { + case "service": + services.add(getAndroidComponent(child, TYPE_SERVICE)); + break; + case "activity": + activities.add(getAndroidComponent(child, TYPE_ACTIVITY)); + break; + case "receiver": + receivers.add(getAndroidComponent(child, TYPE_BROADCAST_RECEIVER)); + break; + case "provider": + providers.add(getAndroidComponent(child, TYPE_CONTENT_PROVIDER)); + break; + case "meta-data": + NamedNodeMap attributes = child.getAttributes(); + metaDatas.put(getAttribute(attributes, "android:name"), getAttribute(attributes, "android:value")); + break; + } + } + } + } + } catch (Exception e) { + throw new ParserException("Error parsing AndroidManifest.xml", e); + } + } + + private String getAndroidComponent(Node node, int type) { + NamedNodeMap attributes = node.getAttributes(); + return getAttribute(attributes, "android:name"); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/builder/PatchBuilder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/builder/PatchBuilder.java new file mode 100644 index 00000000..df530e96 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/builder/PatchBuilder.java @@ -0,0 +1,157 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.builder; + + +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.TypedValue; + +import java.io.File; +import java.io.IOException; + +/** + * @author zhangshaowen + */ +public class PatchBuilder { + private static final String PATCH_NAME = "patch"; + private final Configuration config; + private File unSignedApk; + private File signedApk; + private File signedWith7ZipApk; + private File sevenZipOutPutDir; + + public PatchBuilder(Configuration config) { + this.config = config; + this.unSignedApk = new File(config.mOutFolder, PATCH_NAME + "_unsigned.apk"); + this.signedApk = new File(config.mOutFolder, PATCH_NAME + "_signed.apk"); + this.signedWith7ZipApk = new File(config.mOutFolder, PATCH_NAME + "_signed_7zip.apk"); + this.sevenZipOutPutDir = new File(config.mOutFolder, TypedValue.OUT_7ZIP_FILE_PATH); + } + + public void buildPatch() throws IOException, InterruptedException { + final File resultDir = config.mTempResultDir; + if (!resultDir.exists()) { + throw new IOException(String.format( + "Missing patch unzip files, path=%s\n", resultDir.getAbsolutePath())); + } + //no file change + if (resultDir.listFiles().length == 0) { + return; + } + generalUnsignedApk(unSignedApk); + signApk(unSignedApk, signedApk); + + use7zApk(signedApk, signedWith7ZipApk, sevenZipOutPutDir); + + if (!signedApk.exists()) { + Logger.e("Result: final unsigned patch result: %s, size=%d", unSignedApk.getAbsolutePath(), unSignedApk.length()); + } else { + long length = signedApk.length(); + Logger.e("Result: final signed patch result: %s, size=%d", signedApk.getAbsolutePath(), length); + if (signedWith7ZipApk.exists()) { + long length7zip = signedWith7ZipApk.length(); + Logger.e("Result: final signed with 7zip patch result: %s, size=%d", signedWith7ZipApk.getAbsolutePath(), length7zip); + if (length7zip > length) { + Logger.e("Warning: %s is bigger than %s %d byte, you should choose %s at these time!", + signedWith7ZipApk.getName(), + signedApk.getName(), + (length7zip - length), + signedApk.getName()); + } + } + } + + } + + /** + * @param input unsigned file input + * @param output signed file output + * @throws IOException + * @throws InterruptedException + */ + private void signApk(File input, File output) throws IOException, InterruptedException { + //sign apk + if (config.mUseSignAPk) { + Logger.d("Signing apk: %s", output.getName()); + if (output.exists()) { + output.delete(); + } + String cmd = "jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore " + config.mSignatureFile + + " -storepass " + config.mStorePass + + " -keypass " + config.mKeyPass + + " -signedjar " + output.getAbsolutePath() + + " " + input.getAbsolutePath() + + " " + config.mStoreAlias; + Process pro = Runtime.getRuntime().exec(cmd); + //destroy the stream + pro.waitFor(); + pro.destroy(); + + if (!output.exists()) { + throw new IOException("Can't Generate signed APK. Please check your sign info is correct."); + } + } + } + + /** + * @param output unsigned apk file output + * @throws IOException + */ + private void generalUnsignedApk(File output) throws IOException { + Logger.d("General unsigned apk: %s", output.getName()); + final File tempOutDir = config.mTempResultDir; + if (!tempOutDir.exists()) { + throw new IOException(String.format( + "Missing patch unzip files, path=%s\n", tempOutDir.getAbsolutePath())); + } + FileOperation.zipInputDir(tempOutDir, output); + + if (!output.exists()) { + throw new IOException(String.format( + "can not found the unsigned apk file path=%s", + output.getAbsolutePath())); + } + } + + private void use7zApk(File inputSignedFile, File out7zipFile, File tempFilesDir) throws IOException { + if (!config.mUseSignAPk) { + return; + } + if (!inputSignedFile.exists()) { + throw new IOException( + String.format("can not found the signed apk file to 7z, if you want to use 7z, " + + "you must fill the sign data in the config file path=%s", inputSignedFile.getAbsolutePath()) + ); + } + Logger.d("Try use 7za to compress the patch file: %s, will cost much more time", out7zipFile.getName()); + Logger.d("Current 7za path:%s", config.mSevenZipPath); + + FileOperation.unZipAPk(inputSignedFile.getAbsolutePath(), tempFilesDir.getAbsolutePath()); + //7zip may not enable + if (!FileOperation.sevenZipInputDir(tempFilesDir, out7zipFile, config)) { + return; + } + FileOperation.deleteDir(tempFilesDir); + if (!out7zipFile.exists()) { + throw new IOException(String.format( + "[use7zApk]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s", + out7zipFile.getAbsolutePath())); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ApkDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ApkDecoder.java new file mode 100644 index 00000000..679f3e2f --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ApkDecoder.java @@ -0,0 +1,208 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + + +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.MD5; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; +import com.tencent.tinker.build.util.Utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; + +/** + * Created by zhangshaowen on 16/3/15. + */ +public class ApkDecoder extends BaseDecoder { + private final File mOldApkDir; + private final File mNewApkDir; + + private final ManifestDecoder manifestDecoder; + private final UniqueDexDiffDecoder dexPatchDecoder; + private final BsDiffDecoder soPatchDecoder; + private final ResDiffDecoder resPatchDecoder; + + /** + * if resource's file is also contain in dex or library pattern, + * they won't change in new resources' apk, and we will just warn you. + */ + ArrayList resDuplicateFiles; + + public ApkDecoder(Configuration config) throws IOException { + super(config); + this.mNewApkDir = config.mTempUnzipNewDir; + this.mOldApkDir = config.mTempUnzipOldDir; + + this.manifestDecoder = new ManifestDecoder(config); + + //put meta files in assets + String prePath = TypedValue.FILE_ASSETS + File.separator; + dexPatchDecoder = new UniqueDexDiffDecoder(config, prePath + TypedValue.DEX_META_FILE, TypedValue.DEX_LOG_FILE); + soPatchDecoder = new BsDiffDecoder(config, prePath + TypedValue.SO_META_FILE, TypedValue.SO_LOG_FILE); + resPatchDecoder = new ResDiffDecoder(config, prePath + TypedValue.RES_META_TXT, TypedValue.RES_LOG_FILE); + resDuplicateFiles = new ArrayList<>(); + } + + private void unzipApkFile(File file, File destFile) throws TinkerPatchException, IOException { + String apkName = file.getName(); + if (!apkName.endsWith(TypedValue.FILE_APK)) { + throw new TinkerPatchException( + String.format("input apk file path must end with .apk, yours %s\n", apkName) + ); + } + + String destPath = destFile.getAbsolutePath(); + Logger.d("UnZipping apk to %s", destPath); + FileOperation.unZipAPk(file.getAbsoluteFile().getAbsolutePath(), destPath); + + } + + private void unzipApkFiles(File oldFile, File newFile) throws IOException, TinkerPatchException { + unzipApkFile(oldFile, this.mOldApkDir); + unzipApkFile(newFile, this.mNewApkDir); + } + + private void writeToLogFile(File oldFile, File newFile) throws IOException { + String line1 = "old apk: " + oldFile.getName() + ", size=" + FileOperation.getFileSizes(oldFile) + ", md5=" + MD5.getMD5(oldFile); + String line2 = "new apk: " + newFile.getName() + ", size=" + FileOperation.getFileSizes(newFile) + ", md5=" + MD5.getMD5(newFile); + Logger.d("Analyze old and new apk files:"); + Logger.d(line1); + Logger.d(line2); + Logger.d(""); + } + + @Override + public void onAllPatchesStart() throws IOException, TinkerPatchException { + manifestDecoder.onAllPatchesStart(); + dexPatchDecoder.onAllPatchesStart(); + soPatchDecoder.onAllPatchesStart(); + resPatchDecoder.onAllPatchesStart(); + } + + public boolean patch(File oldFile, File newFile) throws Exception { + writeToLogFile(oldFile, newFile); + //check manifest change first + manifestDecoder.patch(oldFile, newFile); + + unzipApkFiles(oldFile, newFile); + + Files.walkFileTree(mNewApkDir.toPath(), new ApkFilesVisitor(config, mNewApkDir.toPath(), mOldApkDir.toPath(), dexPatchDecoder, soPatchDecoder, resPatchDecoder)); + + //get all duplicate resource file + for (File duplicateRes : resDuplicateFiles) { +// resPatchDecoder.patch(duplicateRes, null); + Logger.e("Warning: res file %s is also match at dex or library pattern, " + + "we treat it as unchanged in the new resource_out.zip", getRelativeStringByOldDir(duplicateRes)); + } + + soPatchDecoder.onAllPatchesEnd(); + dexPatchDecoder.onAllPatchesEnd(); + manifestDecoder.onAllPatchesEnd(); + resPatchDecoder.onAllPatchesEnd(); + + //clean resources + dexPatchDecoder.clean(); + soPatchDecoder.clean(); + resPatchDecoder.clean(); + return true; + } + + @Override + public void onAllPatchesEnd() throws IOException, TinkerPatchException { + } + + class ApkFilesVisitor extends SimpleFileVisitor { + BaseDecoder dexDecoder; + BaseDecoder soDecoder; + BaseDecoder resDecoder; + Configuration config; + Path newApkPath; + Path oldApkPath; + + ApkFilesVisitor(Configuration config, Path newPath, Path oldPath, BaseDecoder dex, BaseDecoder so, BaseDecoder resDecoder) { + this.config = config; + this.dexDecoder = dex; + this.soDecoder = so; + this.resDecoder = resDecoder; + this.newApkPath = newPath; + this.oldApkPath = oldPath; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + + Path relativePath = newApkPath.relativize(file); + + Path oldPath = oldApkPath.resolve(relativePath); + + File oldFile = null; + //is a new file?! + if (oldPath.toFile().exists()) { + oldFile = oldPath.toFile(); + } + String patternKey = relativePath.toString().replace("\\", "/"); + + if (Utils.checkFileInPattern(config.mDexFilePattern, patternKey)) { + //also treat duplicate file as unchanged + if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { + resDuplicateFiles.add(oldFile); + } + + try { + dexDecoder.patch(oldFile, file.toFile()); + } catch (Exception e) { +// e.printStackTrace(); + throw new RuntimeException(e); + } + return FileVisitResult.CONTINUE; + } + if (Utils.checkFileInPattern(config.mSoFilePattern, patternKey)) { + //also treat duplicate file as unchanged + if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { + resDuplicateFiles.add(oldFile); + } + try { + soDecoder.patch(oldFile, file.toFile()); + } catch (Exception e) { +// e.printStackTrace(); + throw new RuntimeException(e); + } + return FileVisitResult.CONTINUE; + } + if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) { + try { + resDecoder.patch(oldFile, file.toFile()); + } catch (Exception e) { +// e.printStackTrace(); + throw new RuntimeException(e); + } + return FileVisitResult.CONTINUE; + } + return FileVisitResult.CONTINUE; + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BaseDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BaseDecoder.java new file mode 100644 index 00000000..d6550847 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BaseDecoder.java @@ -0,0 +1,90 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.TinkerPatchException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +/** + * Created by zhangshaowen on 16/2/28. + */ +public abstract class BaseDecoder { + + protected final Configuration config; + protected final File outDir; + + protected final File resultDir; + + + public BaseDecoder(Configuration config) throws IOException { + this.config = config; + this.outDir = new File(config.mOutFolder); + + this.resultDir = config.mTempResultDir; + + } + + public Configuration getConfig() { + return config; + } + + + protected void clean() { + } + + public Path getRelativePath(File file) { + return config.mTempUnzipNewDir.toPath().relativize(file.toPath()); + } + + public Path getOutputPath(File file) { + return config.mTempResultDir.toPath().resolve(getRelativePath(file)); + } + + public String getRelativeString(File file) { + return config.mTempUnzipNewDir.toPath().relativize(file.toPath()).toString().replace("\\", "/"); + } + + public String getParentRelativeString(File file) { + return config.mTempUnzipNewDir.toPath().relativize(file.getParentFile().toPath()).toString().replace("\\", "/"); + } + + public String getRelativeStringByOldDir(File file) { + return config.mTempUnzipOldDir.toPath().relativize(file.toPath()).toString().replace("\\", "/"); + } + + public String getParentRelativeStringByOldDir(File file) { + return config.mTempUnzipOldDir.toPath().relativize(file.getParentFile().toPath()).toString().replace("\\", "/"); + } + + /** + * 就算前后两个文件都是一样,也会交到这个文件夹 + * + * @param oldFile 如果oldfile 为空,代表这是一个新的文件 + * @param newFile + * @throws IOException + * @throws TinkerPatchException + */ + abstract public boolean patch(File oldFile, File newFile) throws Exception; + + abstract public void onAllPatchesStart() throws Exception; + + abstract public void onAllPatchesEnd() throws Exception; +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BsDiffDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BsDiffDecoder.java new file mode 100644 index 00000000..48e27930 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BsDiffDecoder.java @@ -0,0 +1,150 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + +import com.tencent.tinker.bsdiff.BSDiff; +import com.tencent.tinker.build.info.InfoWriter; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.MD5; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.Utils; + +import java.io.File; +import java.io.IOException; + +/** + * Created by zhangshaowen on 16/2/27. + */ +public class BsDiffDecoder extends BaseDecoder { + private final InfoWriter logWriter; + private final InfoWriter metaWriter; + + public BsDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { + super(config); + + if (metaPath != null) { + metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); + } else { + metaWriter = null; + } + + if (logPath != null) { + logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); + } else { + logWriter = null; + } + } + + @Override + public void clean() { + logWriter.close(); + metaWriter.close(); + } + + @Override + public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { + //first of all, we should check input files + if (newFile == null || !newFile.exists()) { + return false; + } + //new add file + String newMd5 = MD5.getMD5(newFile); + File bsDiffFile = getOutputPath(newFile).toFile(); + + if (oldFile == null || !oldFile.exists()) { + FileOperation.copyFileUsingStream(newFile, bsDiffFile); + writeLogFiles(newFile, null, null, newMd5); + return true; + } + + //both file length is 0 + if (oldFile.length() == 0 && newFile.length() == 0) { + return false; + } + if (oldFile.length() == 0 || newFile.length() == 0) { + FileOperation.copyFileUsingStream(newFile, bsDiffFile); + writeLogFiles(newFile, null, null, newMd5); + return true; + } + + //new add file + String oldMd5 = MD5.getMD5(oldFile); + + if (oldMd5.equals(newMd5)) { + return false; + } + + if (!bsDiffFile.getParentFile().exists()) { + bsDiffFile.getParentFile().mkdirs(); + } + BSDiff.bsdiff(oldFile, newFile, bsDiffFile); + + if (Utils.checkBsDiffFileSize(bsDiffFile, newFile)) { + writeLogFiles(newFile, oldFile, bsDiffFile, newMd5); + } else { + FileOperation.copyFileUsingStream(newFile, bsDiffFile); + writeLogFiles(newFile, null, null, newMd5); + } + return true; + } + + @Override + public void onAllPatchesStart() throws IOException, TinkerPatchException { + + } + + @Override + public void onAllPatchesEnd() throws IOException, TinkerPatchException { + + } + + protected void writeLogFiles(File newFile, File oldFile, File bsDiff, String newMd5) throws IOException { + if (metaWriter == null && logWriter == null) { + return; + } + String parentRelative = getParentRelativeString(newFile); + String relative = getRelativeString(newFile); + + if (metaWriter != null) { + String fileName = newFile.getName(); + + String meta; + if (bsDiff == null || oldFile == null) { + meta = fileName + "," + parentRelative + "," + newMd5 + "," + 0 + "," + 0; + } else { + String oldCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, relative); + if (oldCrc == null || oldCrc.equals("0")) { + throw new TinkerPatchException( + String.format("can't find zipEntry %s from old apk file %s", relative, config.mOldApkFile.getPath()) + ); + } + meta = fileName + "," + parentRelative + "," + newMd5 + "," + oldCrc + "," + MD5.getMD5(bsDiff); + } + Logger.d("BsDiffDecoder:write meta file data: %s", meta); + metaWriter.writeLineToInfoFile(meta); + } + + if (logWriter != null) { + String log = relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newFile) + ", diffSize=" + FileOperation.getFileSizes(bsDiff); + + logWriter.writeLineToInfoFile(log); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/DexDiffDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/DexDiffDecoder.java new file mode 100644 index 00000000..c96a4830 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/DexDiffDecoder.java @@ -0,0 +1,685 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + + +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.DexFormat; +import com.tencent.tinker.android.dx.util.Hex; +import com.tencent.tinker.build.dexpatcher.DexPatchGenerator; +import com.tencent.tinker.build.dexpatcher.util.SmallDexPatchGenerator; +import com.tencent.tinker.build.info.InfoWriter; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.DexClassesComparator; +import com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; +import com.tencent.tinker.build.util.DexClassesComparator.DexGroup; +import com.tencent.tinker.build.util.ExcludedClassModifiedChecker; +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.MD5; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; +import com.tencent.tinker.build.util.Utils; +import com.tencent.tinker.commons.dexpatcher.DexPatchApplier; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger.IDexPatcherLogger; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * Created by zhangshaowen on 2016/3/23. + */ +public class DexDiffDecoder extends BaseDecoder { + private static final String TEST_DEX_PATH = "test.dex"; + private final InfoWriter logWriter; + private final InfoWriter metaWriter; + + private final ExcludedClassModifiedChecker excludedClassModifiedChecker; + + private final Map addedClassDescToDexNameMap; + private final Map deletedClassDescToDexNameMap; + + private final List> oldAndNewDexFilePairList; + + private final Map dexNameToRelatedInfoMap; + private boolean hasDexChanged = false; + private DexPatcherLoggerBridge dexPatcherLoggerBridge = null; + + public DexDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { + super(config); + + if (metaPath != null) { + metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); + } else { + metaWriter = null; + } + + if (logPath != null) { + logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); + } else { + logWriter = null; + } + + if (logWriter != null) { + this.dexPatcherLoggerBridge = new DexPatcherLoggerBridge(logWriter); + } + + excludedClassModifiedChecker = new ExcludedClassModifiedChecker(config); + + addedClassDescToDexNameMap = new HashMap<>(); + deletedClassDescToDexNameMap = new HashMap<>(); + + oldAndNewDexFilePairList = new ArrayList<>(); + + dexNameToRelatedInfoMap = new HashMap<>(); + } + + @Override + public void onAllPatchesStart() throws IOException, TinkerPatchException { + + } + + @SuppressWarnings("NewApi") + @Override + public boolean patch(final File oldFile, final File newFile) throws IOException, TinkerPatchException { + // first of all, we should check input files if excluded classes were modified. + Logger.d("Check for loader classes in dex: %s", + (oldFile == null ? getRelativeString(newFile) : getRelativeString(oldFile)) + ); + + try { + excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile, newFile); + } catch (IOException e) { + throw new TinkerPatchException(e); + } catch (TinkerPatchException e) { + if (config.mIgnoreWarning) { + Logger.e("Warning:ignoreWarning is true, but we found %s", e.getMessage()); + } else { + Logger.e("Warning:ignoreWarning is false, but we found %s", e.getMessage()); + throw e; + } + } catch (Exception e) { + e.printStackTrace(); + } + + // If corresponding new dex was completely deleted, just return false. + // don't process 0 length dex + if (newFile == null || !newFile.exists() || newFile.length() == 0) { + return false; + } + + File dexDiffOut = getOutputPath(newFile).toFile(); + + final String newMd5 = MD5.getMD5(newFile); + + //new add file + if (oldFile == null || !oldFile.exists() || oldFile.length() == 0) { + hasDexChanged = true; + copyNewDexAndMarkInMeta(newFile, newMd5, dexDiffOut); + return true; + } + + final String oldMd5 = MD5.getMD5(oldFile); + + if (!oldMd5.equals(newMd5)) { + hasDexChanged = true; + checkAddedOrDeletedClasses(oldFile, newFile); + } + + RelatedInfo relatedInfo = new RelatedInfo(); + relatedInfo.oldMd5 = oldMd5; + relatedInfo.newMd5 = newMd5; + + // collect current old dex file and corresponding new dex file for further processing. + oldAndNewDexFilePairList.add(new AbstractMap.SimpleEntry<>(oldFile, newFile)); + + final String dexName = oldFile.getName(); + dexNameToRelatedInfoMap.put(dexName, relatedInfo); + + return true; + } + + @SuppressWarnings("NewApi") + @Override + public void onAllPatchesEnd() throws Exception { + if (!hasDexChanged) { + Logger.d("No dexes were changed, nothing needs to be done next."); + return; + } + + File tempFullPatchDexPath = new File(config.mOutFolder + File.separator + TypedValue.DEX_TEMP_PATCH_DIR + File.separator + "full"); + ensureDirectoryExist(tempFullPatchDexPath); + File tempSmallPatchDexPath = new File(config.mOutFolder + File.separator + TypedValue.DEX_TEMP_PATCH_DIR + File.separator + "small"); + ensureDirectoryExist(tempSmallPatchDexPath); + + // Generate dex diff out and full patched dex if a pair of dex is different. + for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { + File oldFile = oldAndNewDexFilePair.getKey(); + File newFile = oldAndNewDexFilePair.getValue(); + final String dexName = oldFile.getName(); + RelatedInfo relatedInfo = dexNameToRelatedInfoMap.get(dexName); + + if (!relatedInfo.oldMd5.equals(relatedInfo.newMd5)) { + File dexDiffOut = getOutputPath(newFile).toFile(); + ensureDirectoryExist(dexDiffOut.getParentFile()); + + try { + DexPatchGenerator dexPatchGen = new DexPatchGenerator(oldFile, newFile); + dexPatchGen.setAdditionalRemovingClassPatterns(config.mDexLoaderPattern); + + logWriter.writeLineToInfoFile( + String.format( + "Start diff between [%s] as old and [%s] as new:", + getRelativeStringBy(oldFile, config.mTempUnzipOldDir), + getRelativeStringBy(newFile, config.mTempUnzipNewDir) + ) + ); + + dexPatchGen.executeAndSaveTo(dexDiffOut); + } catch (Exception e) { + throw new TinkerPatchException(e); + } + + if (!dexDiffOut.exists()) { + throw new TinkerPatchException("can not find the diff file:" + dexDiffOut.getAbsolutePath()); + } + + relatedInfo.dexDiffFile = dexDiffOut; + relatedInfo.dexDiffMd5 = MD5.getMD5(dexDiffOut); + + File tempFullPatchedDexFile = new File(tempFullPatchDexPath, dexName); + + try { + new DexPatchApplier(oldFile, dexDiffOut).executeAndSaveTo(tempFullPatchedDexFile); + + Logger.d( + String.format("Verifying if patched new dex is logically the same as original new dex: %s ...", getRelativeStringBy(newFile, config.mTempUnzipNewDir)) + ); + + Dex origNewDex = new Dex(newFile); + Dex patchedNewDex = new Dex(tempFullPatchedDexFile); + checkDexChange(origNewDex, patchedNewDex); + + relatedInfo.newOrFullPatchedFile = tempFullPatchedDexFile; + relatedInfo.newOrFullPatchedMd5 = MD5.getMD5(tempFullPatchedDexFile); + } catch (Exception e) { + e.printStackTrace(); + throw new TinkerPatchException( + "Failed to generate temporary patched dex, which makes MD5 generating procedure of new dex failed, either.", e + ); + } + + if (!tempFullPatchedDexFile.exists()) { + throw new TinkerPatchException("can not find the temporary full patched dex file:" + tempFullPatchedDexFile.getAbsolutePath()); + } + Logger.e("Gen %s for dalvik full dex file:%s, size:%d, md5:%s", dexName, tempFullPatchedDexFile.getAbsolutePath(), tempFullPatchedDexFile.length(), relatedInfo.newOrFullPatchedMd5); + } else { + // In this case newDexFile is the same as oldDexFile, but we still + // need to treat it as patched dex file so that the SmallPatchGenerator + // can analyze which class of this dex should be kept in small patch. + relatedInfo.newOrFullPatchedFile = newFile; + relatedInfo.newOrFullPatchedMd5 = relatedInfo.newMd5; + } + } + + Set classNOldDexFiles = new HashSet<>(); + + for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { + File oldFile = oldAndNewDexFilePair.getKey(); + final String dexName = oldFile.getName(); + + if (isDexNameMatchesClassNPattern(dexName)) { + classNOldDexFiles.add(oldFile); + } + } + + // If we meet a case like: + // classes.dex, classes2.dex, classes4.dex, classes5.dex + // Since classes3.dex is missing, according to the logic in AOSP, we should not treat + // rest dexes as part of class N dexes. + Map dexNameToClassNOldDexFileMap = new HashMap<>(); + for (File classNOldDex : classNOldDexFiles) { + dexNameToClassNOldDexFileMap.put(classNOldDex.getName(), classNOldDex); + } + + boolean isRestDexNotInClassN = false; + for (int i = 0; i < classNOldDexFiles.size(); ++i) { + final String expectedDexName = (i == 0 ? DexFormat.DEX_IN_JAR_NAME : "classes" + (i + 1) + ".dex"); + if (!dexNameToClassNOldDexFileMap.containsKey(expectedDexName)) { + isRestDexNotInClassN = true; + } else { + if (isRestDexNotInClassN) { + File mistakenClassNOldDexFile = dexNameToClassNOldDexFileMap.get(expectedDexName); + classNOldDexFiles.remove(mistakenClassNOldDexFile); + } + } + } + + File tempSmallPatchInfoFile = new File(config.mTempResultDir, TypedValue.DEX_SMALLPATCH_INFO_FILE); + ensureDirectoryExist(tempSmallPatchInfoFile.getParentFile()); + + // So far we know whether a pair of dex is belong to class N dexes or other dexes. + // Then we collect class N dex pairs and other dex pairs by separate their old dex + // and full patched dex into different list. + SmallDexPatchGenerator smallDexPatchGenerator = new SmallDexPatchGenerator(); + smallDexPatchGenerator.setLoaderClassPatterns(config.mDexLoaderPattern); + smallDexPatchGenerator.setLogger(dexPatcherLoggerBridge); + + logWriter.writeLineToInfoFile("\nStart collecting old dex and full patched dex..."); + + List classNOldDexFileList = new ArrayList<>(); + List classNFullPatchedDexFileList = new ArrayList<>(); + List otherOldDexFileList = new ArrayList<>(); + List otherFullPatchedDexFileList = new ArrayList<>(); + for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { + File oldFile = oldAndNewDexFilePair.getKey(); + final String dexName = oldFile.getName(); + File fullPatchedFile = dexNameToRelatedInfoMap.get(dexName).newOrFullPatchedFile; + if (classNOldDexFiles.contains(oldFile)) { + classNOldDexFileList.add(oldFile); + classNFullPatchedDexFileList.add(fullPatchedFile); + } else { + otherOldDexFileList.add(oldFile); + otherFullPatchedDexFileList.add(fullPatchedFile); + } + } + + logWriter.writeLineToInfoFile(String.format("\nCollected class N old dexes: %s", classNOldDexFileList)); + logWriter.writeLineToInfoFile(String.format("Collected class N full patched dexes: %s", classNFullPatchedDexFileList)); + logWriter.writeLineToInfoFile(String.format("\nCollected other old dexes: %s", otherOldDexFileList)); + logWriter.writeLineToInfoFile(String.format("Collected other full patched dexes: %s", otherFullPatchedDexFileList)); + + smallDexPatchGenerator.appendDexGroup(DexGroup.wrap(classNOldDexFileList), DexGroup.wrap(classNFullPatchedDexFileList)); + + if (!otherOldDexFileList.isEmpty()) { + smallDexPatchGenerator.appendDexGroup(DexGroup.wrap(otherOldDexFileList), DexGroup.wrap(otherFullPatchedDexFileList)); + } + + try { + Logger.d("Start generating small patch info file..."); + smallDexPatchGenerator.executeAndSaveTo(tempSmallPatchInfoFile); + } catch (Exception e) { + throw new TinkerPatchException("\nFailed to generate small patch info file.", e); + } + if (!tempSmallPatchInfoFile.exists()) { + throw new TinkerPatchException("can not find the small patch info file:" + tempSmallPatchInfoFile.getAbsolutePath()); + } + + SmallPatchedDexItemFile smallPatchedDexItemFile = new SmallPatchedDexItemFile(tempSmallPatchInfoFile); + + // Generate small patched dex and write meta. + for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { + File oldFile = oldAndNewDexFilePair.getKey(); + File newFile = oldAndNewDexFilePair.getValue(); + final String dexName = oldFile.getName(); + final String oldDexSignStr = Hex.toHexString(new Dex(oldFile).computeSignature(false)); + File tempSmallPatchedFile = new File(tempSmallPatchDexPath, dexName); + RelatedInfo relatedInfo = dexNameToRelatedInfoMap.get(dexName); + File dexDiffFile = relatedInfo.dexDiffFile; + + if (!smallPatchedDexItemFile.isSmallPatchedDexEmpty(oldDexSignStr)) { + try { + new DexPatchApplier(oldFile, dexDiffFile, smallPatchedDexItemFile).executeAndSaveTo(tempSmallPatchedFile); + } catch (Exception e) { + e.printStackTrace(); + throw new TinkerPatchException( + "Failed to generate temporary small patched dex, which makes MD5 generating procedure of small patched dex failed, either.", e + ); + } + if (!tempSmallPatchedFile.exists()) { + throw new TinkerPatchException("can not find the temporary small patched dex file:" + tempSmallPatchInfoFile.getAbsolutePath()); + } + relatedInfo.smallPatchedMd5 = MD5.getMD5(tempSmallPatchedFile); + Logger.e("Gen %s for art small dex file:%s, size:%d, md5:%s", dexName, tempSmallPatchedFile.getAbsolutePath(), tempSmallPatchedFile.length(), relatedInfo.smallPatchedMd5); + + if (relatedInfo.oldMd5.equals(relatedInfo.newMd5)) { + // Unmodified dex, which has no dexDiffFile, and is ignored in dvm environment. + // So we pass zero string to destMd5InDvm and dexDiffMd5. + writeLogFiles(newFile, oldFile, relatedInfo.dexDiffFile, "0", relatedInfo.smallPatchedMd5, "0"); + } else { + writeLogFiles(newFile, oldFile, relatedInfo.dexDiffFile, relatedInfo.newOrFullPatchedMd5, relatedInfo.smallPatchedMd5, relatedInfo.dexDiffMd5); + } + } + } + + addTestDex(); + + // Here we will check if any classes that were deleted in one dex + // would be added to another dex. e.g. classA is deleted in dex0 and + // added in dex1. + // Since DexClassesComparator will guarantee that a class can be either 'added' + // or 'deleted' between two files it compares. We can achieve our checking works + // by calculating the intersection of deletedClassDescs and addedClassDescs. + Set deletedClassDescs = new HashSet(deletedClassDescToDexNameMap.keySet()); + Set addedClassDescs = new HashSet(addedClassDescToDexNameMap.keySet()); + deletedClassDescs.retainAll(addedClassDescs); + + // So far deletedClassNames only contains the intersect elements between + // deletedClassNames and addedClassNames. + Set movedCrossFilesClassDescs = deletedClassDescs; + if (!movedCrossFilesClassDescs.isEmpty()) { + Logger.e("Warning:Class Moved. Some classes are just moved from one dex to another. " + + "This behavior may leads to unnecessary enlargement of patch file. you should try to check them:"); + + for (String classDesc : movedCrossFilesClassDescs) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + sb.append("classDesc:").append(classDesc).append(','); + sb.append("from:").append(deletedClassDescToDexNameMap.get(classDesc)).append(','); + sb.append("to:").append(addedClassDescToDexNameMap.get(classDesc)); + sb.append('}'); + Logger.e(sb.toString()); + } + } + } + + @Override + public void clean() { + metaWriter.close(); + logWriter.close(); + } + + private void ensureDirectoryExist(File dir) { + if (!dir.exists()) { + if (!dir.mkdirs()) { + throw new TinkerPatchException("failed to create directory: " + dir); + } + } + } + + private boolean isDexNameMatchesClassNPattern(String dexName) { + return (dexName.matches("^classes[0-9]*\\.dex$")); + } + + private void copyNewDexAndMarkInMeta(File newFile, String newMd5, File output) throws IOException { + newMd5 = checkNewDexAndMd5(newMd5, newFile); + FileOperation.copyFileUsingStream(newFile, output); + writeLogFiles(newFile, null, null, newMd5, newMd5, "0"); + } + + private void checkAddedOrDeletedClasses(File oldFile, File newFile) throws IOException { + // Before starting real diff works, we collect added class descriptor + // and deleted class descriptor for further analysing. + Dex oldDex = new Dex(oldFile); + Dex newDex = new Dex(newFile); + + Set oldClassDescs = new HashSet<>(); + for (ClassDef oldClassDef : oldDex.classDefs()) { + oldClassDescs.add(oldDex.typeNames().get(oldClassDef.typeIndex)); + } + + Set newClassDescs = new HashSet<>(); + for (ClassDef newClassDef : newDex.classDefs()) { + newClassDescs.add(newDex.typeNames().get(newClassDef.typeIndex)); + } + + Set addedClassDescs = new HashSet<>(newClassDescs); + addedClassDescs.removeAll(oldClassDescs); + + Set deletedClassDescs = new HashSet<>(oldClassDescs); + deletedClassDescs.removeAll(newClassDescs); + + for (String addedClassDesc : addedClassDescs) { + if (addedClassDescToDexNameMap.containsKey(addedClassDesc)) { + throw new TinkerPatchException( + String.format( + "Class Duplicate. Class [%s] is added in both new dex: [%s] and [%s]. Please check your newly apk.", + addedClassDesc, + addedClassDescToDexNameMap.get(addedClassDesc), + newFile.toString() + ) + ); + } else { + addedClassDescToDexNameMap.put(addedClassDesc, newFile.toString()); + } + } + + for (String deletedClassDesc : deletedClassDescs) { + if (deletedClassDescToDexNameMap.containsKey(deletedClassDesc)) { + throw new TinkerPatchException( + String.format( + "Class Duplicate. Class [%s] is deleted in both old dex: [%s] and [%s]. Please check your base apk.", + deletedClassDesc, + addedClassDescToDexNameMap.get(deletedClassDesc), + oldFile.toString() + ) + ); + } else { + deletedClassDescToDexNameMap.put(deletedClassDesc, newFile.toString()); + } + } + } + + private void checkDexChange(Dex originDex, Dex newDex) { + DexClassesComparator classesCmptor = new DexClassesComparator("*"); + classesCmptor.setIgnoredRemovedClassDescPattern(config.mDexLoaderPattern); + classesCmptor.startCheck(originDex, newDex); + + List addedClassInfos = classesCmptor.getAddedClassInfos(); + boolean isNoClassesAdded = addedClassInfos.isEmpty(); + + Map changedClassDescToClassInfosMap; + boolean isNoClassesChanged; + + if (isNoClassesAdded) { + changedClassDescToClassInfosMap = classesCmptor.getChangedClassDescToInfosMap(); + isNoClassesChanged = changedClassDescToClassInfosMap.isEmpty(); + } else { + throw new TinkerPatchException( + "some classes was unexpectedly added in patched new dex, check if there's any bugs in " + + "patch algorithm. Related classes: " + Utils.collectionToString(addedClassInfos) + ); + } + + if (isNoClassesChanged) { + List deletedClassInfos = classesCmptor.getDeletedClassInfos(); + if (!deletedClassInfos.isEmpty()) { + throw new TinkerPatchException( + "some classes that are not matched to loader class pattern " + + "was unexpectedly deleted in patched new dex, check if there's any bugs in " + + "patch algorithm. Related classes: " + + Utils.collectionToString(deletedClassInfos) + ); + } + } else { + throw new TinkerPatchException( + "some classes was unexpectedly changed in patched new dex, check if there's any bugs in " + + "patch algorithm. Related classes: " + + Utils.collectionToString(changedClassDescToClassInfosMap.keySet()) + ); + } + } + + private void addTestDex() throws IOException { + //write test dex + String dexMode = "jar"; + if (config.mDexRaw) { + dexMode = "raw"; + } + + final InputStream is = DexDiffDecoder.class.getResourceAsStream("/" + TEST_DEX_PATH); + String md5 = MD5.getMD5(is, 1024); + is.close(); + + String meta = TEST_DEX_PATH + "," + "" + "," + md5 + "," + md5 + "," + 0 + "," + 0 + "," + dexMode; + + File dest = new File(config.mTempResultDir + "/" + TEST_DEX_PATH); + FileOperation.copyResourceUsingStream(TEST_DEX_PATH, dest); + Logger.d("Add test install result dex: %s, size:%d", dest.getAbsolutePath(), dest.length()); + Logger.d("DexDecoder:write test dex meta file data: %s", meta); + + metaWriter.writeLineToInfoFile(meta); + } + + private String checkNewDexAndMd5(String md5, File dexFile) { + String name = dexFile.getName(); + if (name.endsWith(".dex")) { + return md5; + } else { + try { + final JarFile dexJar = new JarFile(dexFile); + ZipEntry classesDex = dexJar.getEntry(DexFormat.DEX_IN_JAR_NAME); + // no code + if (null == classesDex) { + throw new TinkerPatchException( + String.format("dex jar file %s do not contain 'classes.dex', it is not a correct dex jar file!", dexFile.getAbsolutePath()) + ); + } + + return MD5.getMD5(dexJar.getInputStream(classesDex), 1024 * 100); + } catch (IOException e) { + throw new TinkerPatchException( + String.format("dex file %s is not end with '.dex', but it is not a correct dex jar file also!", dexFile.getAbsolutePath()), e + ); + } + } + } + + private String getRelativeStringBy(File file, File reference) { + File actualReference = reference.getParentFile(); + if (actualReference == null) { + actualReference = reference; + } + return actualReference.toPath().relativize(file.toPath()).toString().replace("\\", "/"); + } + + /** + * Construct dex meta-info and write it to meta file and log. + * + * @param newOrFullPatchedFile + * New dex file or full patched dex file. + * @param oldFile + * Old dex file. + * @param dexDiffFile + * Dex diff file. (Generated by DexPatchGenerator.) + * @param destMd5InDvm + * Md5 of output dex in dvm environment, could be full patched dex md5 or new dex. + * @param destMd5InArt + * Md5 of output dex in dvm environment, could be small patched dex md5 or new dex. + * @param dexDiffMd5 + * Md5 of dex patch info file. + * + * @throws IOException + */ + protected void writeLogFiles(File newOrFullPatchedFile, File oldFile, File dexDiffFile, String destMd5InDvm, String destMd5InArt, String dexDiffMd5) throws IOException { + if (metaWriter == null && logWriter == null) { + return; + } + String parentRelative = getParentRelativeString(newOrFullPatchedFile); + String relative = getRelativeString(newOrFullPatchedFile); + + if (metaWriter != null) { + String fileName = newOrFullPatchedFile.getName(); + String dexMode = "jar"; + if (config.mDexRaw) { + dexMode = "raw"; + } + + //new file + String oldCrc; + if (oldFile == null) { + oldCrc = "0"; + Logger.d("DexDecoder:add newly dex file: %s", parentRelative); + } else { + oldCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, relative); + if (oldCrc == null || oldCrc.equals("0")) { + throw new TinkerPatchException( + String.format("can't find zipEntry %s from old apk file %s", relative, config.mOldApkFile.getPath()) + ); + } + } + + String meta = fileName + "," + parentRelative + "," + destMd5InDvm + "," + destMd5InArt + "," + dexDiffMd5 + "," + oldCrc + "," + dexMode; + + Logger.d("DexDecoder:write meta file data: %s", meta); + metaWriter.writeLineToInfoFile(meta); + } + + if (logWriter != null) { + String log = relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newOrFullPatchedFile) + ", diffSize=" + FileOperation.getFileSizes(dexDiffFile); + + logWriter.writeLineToInfoFile(log); + } + } + + private final class RelatedInfo { + File newOrFullPatchedFile = null; + /** + * This field could be null if old dex and new dex + * are the same. + */ + File dexDiffFile = null; + String oldMd5 = "0"; + String newMd5 = "0"; + String dexDiffMd5 = "0"; + /** + * This field could be one of the following value: + * fullPatchedDex md5, if old dex and new dex are different; + * newDex md5, if new dex is marked to be copied directly; + */ + String newOrFullPatchedMd5 = "0"; + String smallPatchedMd5 = "0"; + } + + private final class DexPatcherLoggerBridge implements IDexPatcherLogger { + private final InfoWriter logWritter; + + DexPatcherLoggerBridge(InfoWriter logWritter) { + this.logWritter = logWritter; + } + + @Override + public void v(String msg) { + this.logWritter.writeLineToInfoFile(msg); + } + + @Override + public void d(String msg) { + this.logWritter.writeLineToInfoFile(msg); + } + + @Override + public void i(String msg) { + this.logWritter.writeLineToInfoFile(msg); + } + + @Override + public void w(String msg) { + this.logWritter.writeLineToInfoFile(msg); + } + + @Override + public void e(String msg) { + this.logWritter.writeLineToInfoFile(msg); + } + } +} + + diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ManifestDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ManifestDecoder.java new file mode 100644 index 00000000..d21414ee --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ManifestDecoder.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + + +import com.tencent.tinker.build.apkparser.AndroidManifest; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +/** + * Created by zhangshaowen on 16/4/6. + */ + +public class ManifestDecoder extends BaseDecoder { + + public ManifestDecoder(Configuration config) throws IOException { + super(config); + } + + @Override + public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { + final boolean ignoreWarning = config.mIgnoreWarning; + try { + AndroidManifest oldAndroidManifest = AndroidManifest.getAndroidManifest(oldFile); + AndroidManifest newAndroidManifest = AndroidManifest.getAndroidManifest(newFile); + //check minSdkVersion + int minSdkVersion = Integer.parseInt(oldAndroidManifest.apkMeta.getMinSdkVersion()); + + if (minSdkVersion < TypedValue.ANDROID_40_API_LEVEL) { + if (config.mDexRaw) { + if (ignoreWarning) { + //ignoreWarning, just log + Logger.e("Warning:ignoreWarning is true, but your old apk's minSdkVersion %d is below 14, you should set the dexMode to 'jar', otherwise, it will be crash at some times", minSdkVersion); + } else { + Logger.e("Warning:ignoreWarning is false, but your old apk's minSdkVersion %d is below 14, you should set the dexMode to 'jar', otherwise, it will be crash at some times", minSdkVersion); + + throw new TinkerPatchException( + String.format("ignoreWarning is false, but your old apk's minSdkVersion %d is below 14, you should set the dexMode to 'jar', otherwise, it will be crash at some times", minSdkVersion) + ); + } + } + } + + //check whether there is any new Android Component + List oldAndroidComponent = oldAndroidManifest.getComponents(); + List newAndroidComponent = newAndroidManifest.getComponents(); + + for (String newComponentName : newAndroidComponent) { + boolean found = false; + for (String oldComponentName : oldAndroidComponent) { + if (newComponentName.equals(oldComponentName)) { + found = true; + break; + } + } + if (!found) { + if (ignoreWarning) { + Logger.e("Warning:ignoreWarning is true, but we found a new AndroidComponent %s, it will be crash at some times", newComponentName); + } else { + Logger.e("Warning:ignoreWarning is false, but we found a new AndroidComponent %s, it will be crash at some times", newComponentName); + throw new TinkerPatchException( + String.format("ignoreWarning is false, but we found a new AndroidComponent %s, it will be crash at some times", newComponentName) + ); + } + } + } + + } catch (ParseException e) { + e.printStackTrace(); + throw new TinkerPatchException("parse android manifest error!"); + } + return false; + } + + @Override + public void onAllPatchesStart() throws IOException, TinkerPatchException { + + } + + @Override + public void onAllPatchesEnd() throws IOException, TinkerPatchException { + + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ResDiffDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ResDiffDecoder.java new file mode 100644 index 00000000..193b41c1 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ResDiffDecoder.java @@ -0,0 +1,407 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + +import com.tencent.tinker.bsdiff.BSDiff; +import com.tencent.tinker.build.info.InfoWriter; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.MD5; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; +import com.tencent.tinker.build.util.Utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Created by zhangshaowen on 16/8/8. + */ +public class ResDiffDecoder extends BaseDecoder { + + private static final String TEMP_RES_ZIP = "temp_res.zip"; + private static final String TEMP_RES_7ZIP = "temp_res_7ZIP.zip"; + private final InfoWriter logWriter; + private final InfoWriter metaWriter; + private ArrayList addedSet; + private ArrayList modifiedSet; + private ArrayList largeModifiedSet; + private HashMap largeModifiedMap; + private ArrayList deletedSet; + + public ResDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { + super(config); + + if (metaPath != null) { + metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); + } else { + metaWriter = null; + } + + if (logPath != null) { + logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); + } else { + logWriter = null; + } + addedSet = new ArrayList<>(); + modifiedSet = new ArrayList<>(); + largeModifiedSet = new ArrayList<>(); + largeModifiedMap = new HashMap<>(); + deletedSet = new ArrayList<>(); + } + + @Override + public void clean() { + metaWriter.close(); + logWriter.close(); + } + + private boolean checkLargeModFile(File file) { + long lenght = file.length(); + if (lenght > config.mLargeModSize * TypedValue.K_BYTES) { + return true; + } + return false; + } + + @Override + public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { + //actually, it won't go below + if (newFile == null || !newFile.exists()) { + String name = getRelativeStringByOldDir(oldFile); + if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { + Logger.e("found delete resource: " + name + " ,but it match ignore change pattern, just ignore!"); + return false; + } + deletedSet.add(name); + writeResLog(newFile, oldFile, TypedValue.DEL); + return true; + } + + File outputFile = getOutputPath(newFile).toFile(); + + if (oldFile == null || !oldFile.exists()) { + String name = getRelativeString(newFile); + if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { + Logger.e("found add resource: " + name + " ,but it match ignore change pattern, just ignore!"); + return false; + } + FileOperation.copyFileUsingStream(newFile, outputFile); + addedSet.add(name); + writeResLog(newFile, oldFile, TypedValue.ADD); + return true; + } + //both file length is 0 + if (oldFile.length() == 0 && newFile.length() == 0) { + return false; + } + //new add file + String newMd5 = MD5.getMD5(newFile); + String oldMd5 = MD5.getMD5(oldFile); + + //oldFile or newFile may be 0b length + if (oldMd5 != null && oldMd5.equals(newMd5)) { + return false; + } + String name = getRelativeString(newFile); + if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { + Logger.e("found modify resource: " + name + " ,but it match ignore change pattern, just ignore!"); + return false; + } + if (name.equals(TypedValue.RES_MANIFEST)) { + Logger.e("found modify resource: " + name + " ,but it is AndroidManifest.xml, just ignore!"); + return false; + } + if (checkLargeModFile(newFile)) { + if (!outputFile.getParentFile().exists()) { + outputFile.getParentFile().mkdirs(); + } + BSDiff.bsdiff(oldFile, newFile, outputFile); + //treat it as normal modify + if (Utils.checkBsDiffFileSize(outputFile, newFile)) { + LargeModeInfo largeModeInfo = new LargeModeInfo(); + largeModeInfo.path = newFile; + largeModeInfo.crc = FileOperation.getFileCrc32(newFile); + largeModeInfo.md5 = newMd5; + largeModifiedSet.add(name); + largeModifiedMap.put(name, largeModeInfo); + writeResLog(newFile, oldFile, TypedValue.LARGE_MOD); + return true; + } + } + modifiedSet.add(name); + FileOperation.copyFileUsingStream(newFile, outputFile); + writeResLog(newFile, oldFile, TypedValue.MOD); + return true; + } + + private void writeResLog(File newFile, File oldFile, int mode) throws IOException { + if (logWriter != null) { + String log = ""; + String relative; + switch (mode) { + case TypedValue.ADD: + relative = getRelativeString(newFile); + Logger.d("Found add resource: " + relative); + log = "add resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newFile); + break; + case TypedValue.MOD: + relative = getRelativeString(newFile); + Logger.d("Found modify resource: " + relative); + log = "modify resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newFile); + break; + case TypedValue.DEL: + relative = getRelativeStringByOldDir(oldFile); + Logger.d("Found deleted resource: " + relative); + log = "deleted resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newFile); + break; + case TypedValue.LARGE_MOD: + relative = getRelativeString(newFile); + Logger.d("Found large modify resource: " + relative + " size:" + newFile.length()); + log = "large modify resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + + FileOperation.getFileSizes(newFile); + break; + } + logWriter.writeLineToInfoFile(log); + } + } + + @Override + public void onAllPatchesStart() throws IOException, TinkerPatchException { + + } + + @Override + public void onAllPatchesEnd() throws IOException, TinkerPatchException { + //only there is only deleted set, we just ignore + if (addedSet.isEmpty() && modifiedSet.isEmpty() && largeModifiedSet.isEmpty()) { + return; + } + + if (!config.mResRawPattern.contains(TypedValue.RES_ARSC)) { + throw new TinkerPatchException("resource must contain resources.arsc pattern"); + } + if (!config.mResRawPattern.contains(TypedValue.RES_MANIFEST)) { + throw new TinkerPatchException("resource must contain AndroidManifest.xml pattern"); + } + + //check gradle build + if (config.mUsingGradle) { + final boolean resourceArscChanged = modifiedSet.contains(TypedValue.RES_ARSC) + || largeModifiedSet.contains(TypedValue.RES_ARSC); + final boolean ignoreWarning = config.mIgnoreWarning; + + if (resourceArscChanged && !config.mUseApplyResource) { + if (ignoreWarning) { + //ignoreWarning, just log + Logger.e("Warning:ignoreWarning is true, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times"); + } else { + Logger.e("Warning:ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times"); + + throw new TinkerPatchException( + String.format("ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times") + ); + } + } /*else if (config.mUseApplyResource) { + int totalChangeSize = addedSet.size() + modifiedSet.size() + largeModifiedSet.size(); + if (totalChangeSize == 1 && resourceArscChanged) { + Logger.e("Warning: we are using applyResourceMapping mode to build the new apk, but there is only resources.arsc changed, you should ensure there is actually resource changed!"); + } + }*/ + } + //add delete set + deletedSet.addAll(getDeletedResource(config.mTempUnzipOldDir, config.mTempUnzipNewDir)); + + //we can't modify AndroidManifest file + addedSet.remove(TypedValue.RES_MANIFEST); + deletedSet.remove(TypedValue.RES_MANIFEST); + modifiedSet.remove(TypedValue.RES_MANIFEST); + largeModifiedSet.remove(TypedValue.RES_MANIFEST); + //remove add, delete or modified if they are in ignore change pattern also + removeIgnoreChangeFile(modifiedSet); + removeIgnoreChangeFile(deletedSet); + removeIgnoreChangeFile(addedSet); + removeIgnoreChangeFile(largeModifiedSet); + + File tempResZip = new File(config.mOutFolder + File.separator + TEMP_RES_ZIP); + final File tempResFiles = config.mTempResultDir; + + //gen zip resources_out.zip + FileOperation.zipInputDir(tempResFiles, tempResZip); + File extractToZip = new File(config.mOutFolder + File.separator + TypedValue.RES_OUT); + + String resZipMd5 = Utils.genResOutputFile(extractToZip, tempResZip, config, + addedSet, modifiedSet, deletedSet, largeModifiedSet, largeModifiedMap); + + Logger.e("final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5); + logWriter.writeLineToInfoFile( + String.format("final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5) + ); + //delete temp file + FileOperation.deleteFile(tempResZip); + + //gen zip resources_out_7z.zip + File extractTo7Zip = new File(config.mOutFolder + File.separator + TypedValue.RES_OUT_7ZIP); + File tempRes7Zip = new File(config.mOutFolder + File.separator + TEMP_RES_7ZIP); + + //ensure 7zip is enable + if (FileOperation.sevenZipInputDir(tempResFiles, tempRes7Zip, config)) { + //7zip whether actual exist + if (tempRes7Zip.exists()) { + + String res7zipMd5 = Utils.genResOutputFile(extractTo7Zip, tempRes7Zip, config, + addedSet, modifiedSet, deletedSet, largeModifiedSet, largeModifiedMap); + //delete temp file + FileOperation.deleteFile(tempRes7Zip); + Logger.e("final 7zip resource: %s, size=%d, md5=%s", extractTo7Zip.getName(), extractTo7Zip.length(), res7zipMd5); + logWriter.writeLineToInfoFile( + String.format("final 7zip resource: %s, size=%d, md5=%s", extractTo7Zip.getName(), extractTo7Zip.length(), res7zipMd5) + ); + } + } + //first, write resource meta first + //use resources.arsc's base crc to identify base.apk + String arscBaseCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, TypedValue.RES_ARSC); + String arscMd5 = FileOperation.getZipEntryMd5(extractToZip, TypedValue.RES_ARSC); + if (arscBaseCrc == null || arscMd5 == null) { + throw new TinkerPatchException("can't find resources.arsc's base crc or md5"); + } + + String resourceMeta = Utils.getResourceMeta(arscBaseCrc, arscMd5); + writeMetaFile(resourceMeta); + + //pattern + String patternMeta = TypedValue.PATTERN_TITLE; + HashSet patterns = new HashSet<>(config.mResRawPattern); + //we will process them separate + patterns.remove(TypedValue.RES_MANIFEST); + + writeMetaFile(patternMeta + patterns.size()); + //write pattern + for (String item : patterns) { + writeMetaFile(item); + } + //write meta file, write large modify first + writeMetaFile(largeModifiedSet, TypedValue.LARGE_MOD); + writeMetaFile(modifiedSet, TypedValue.MOD); + writeMetaFile(addedSet, TypedValue.ADD); + writeMetaFile(deletedSet, TypedValue.DEL); + } + + private void removeIgnoreChangeFile(ArrayList array) { + ArrayList removeList = new ArrayList<>(); + for (String name : array) { + if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { + Logger.e("ignore change resource file: " + name); + removeList.add(name); + } + } + array.removeAll(removeList); + } + + private void writeMetaFile(String line) { + metaWriter.writeLineToInfoFile(line); + } + + private void writeMetaFile(ArrayList set, int mode) { + if (!set.isEmpty()) { + String title = ""; + switch (mode) { + case TypedValue.ADD: + title = TypedValue.ADD_TITLE + set.size(); + break; + case TypedValue.MOD: + title = TypedValue.MOD_TITLE + set.size(); + break; + case TypedValue.LARGE_MOD: + title = TypedValue.LARGE_MOD_TITLE + set.size(); + break; + case TypedValue.DEL: + title = TypedValue.DEL_TITLE + set.size(); + break; + } + metaWriter.writeLineToInfoFile(title); + for (String name : set) { + String line = name; + if (mode == TypedValue.LARGE_MOD) { + LargeModeInfo info = largeModifiedMap.get(name); + line = name + "," + info.md5 + "," + info.crc; + } + metaWriter.writeLineToInfoFile(line); + } + } + } + + public ArrayList getDeletedResource(File oldApkDir, File newApkDir) throws IOException { + //get deleted resource + DeletedResVisitor deletedResVisitor = new DeletedResVisitor(config, newApkDir.toPath(), oldApkDir.toPath()); + Files.walkFileTree(oldApkDir.toPath(), deletedResVisitor); + return deletedResVisitor.deletedFiles; + } + + public class LargeModeInfo { + public File path = null; + public long crc; + public String md5 = null; + } + + class DeletedResVisitor extends SimpleFileVisitor { + Configuration config; + Path newApkPath; + Path oldApkPath; + ArrayList deletedFiles; + + DeletedResVisitor(Configuration config, Path newPath, Path oldPath) { + this.config = config; + this.newApkPath = newPath; + this.oldApkPath = oldPath; + this.deletedFiles = new ArrayList<>(); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + + Path relativePath = oldApkPath.relativize(file); + + Path newPath = newApkPath.resolve(relativePath); + + String patternKey = relativePath.toString().replace("\\", "/"); + + if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) { + //not contain in new path, is deleted + if (!newPath.toFile().exists()) { + deletedFiles.add(relativePath.toString()); + writeResLog(newPath.toFile(), file.toFile(), TypedValue.DEL); + } + return FileVisitResult.CONTINUE; + } + return FileVisitResult.CONTINUE; + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/UniqueDexDiffDecoder.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/UniqueDexDiffDecoder.java new file mode 100644 index 00000000..46a61f7c --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/UniqueDexDiffDecoder.java @@ -0,0 +1,51 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.decoder; + +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.TinkerPatchException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Created by zhangshaowen on 16/3/9. + */ +public class UniqueDexDiffDecoder extends DexDiffDecoder { + private ArrayList addedDexFiles; + + public UniqueDexDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { + super(config, metaPath, logPath); + addedDexFiles = new ArrayList<>(); + } + + @Override + public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { + boolean added = super.patch(oldFile, newFile); + if (added) { + String name = newFile.getName(); + if (addedDexFiles.contains(name)) { + throw new TinkerPatchException("illegal dex name, dex name should be unique, dex:" + name); + } else { + addedDexFiles.add(name); + } + } + return added; + } + +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/DexPatchGenerator.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/DexPatchGenerator.java new file mode 100644 index 00000000..8bbe973b --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/DexPatchGenerator.java @@ -0,0 +1,720 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSetRefListSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSetSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationsDirectorySectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.ClassDataSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.ClassDefSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.CodeSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.DebugInfoItemSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.DexSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.FieldIdSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.MethodIdSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.ProtoIdSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.StaticValueSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.StringDataSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.TypeIdSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.algorithms.diff.TypeListSectionDiffAlgorithm; +import com.tencent.tinker.build.dexpatcher.util.PatternUtils; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.PatchOperation; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class DexPatchGenerator { + private static final String TAG = "DexPatchGenerator"; + + private final Dex oldDex; + private final Dex newDex; + private final DexPatcherLogger logger = new DexPatcherLogger(); + private DexSectionDiffAlgorithm stringDataSectionDiffAlg; + private DexSectionDiffAlgorithm typeIdSectionDiffAlg; + private DexSectionDiffAlgorithm protoIdSectionDiffAlg; + private DexSectionDiffAlgorithm fieldIdSectionDiffAlg; + private DexSectionDiffAlgorithm methodIdSectionDiffAlg; + private DexSectionDiffAlgorithm classDefSectionDiffAlg; + private DexSectionDiffAlgorithm typeListSectionDiffAlg; + private DexSectionDiffAlgorithm annotationSetRefListSectionDiffAlg; + private DexSectionDiffAlgorithm annotationSetSectionDiffAlg; + private DexSectionDiffAlgorithm classDataSectionDiffAlg; + private DexSectionDiffAlgorithm codeSectionDiffAlg; + private DexSectionDiffAlgorithm debugInfoSectionDiffAlg; + private DexSectionDiffAlgorithm annotationSectionDiffAlg; + private DexSectionDiffAlgorithm encodedArraySectionDiffAlg; + private DexSectionDiffAlgorithm annotationsDirectorySectionDiffAlg; + private Set additionalRemovingClassPatternSet; + private int patchedHeaderOffset = 0; + private int patchedStringIdsOffset = 0; + private int patchedTypeIdsOffset = 0; + private int patchedProtoIdsOffset = 0; + private int patchedFieldIdsOffset = 0; + private int patchedMethodIdsOffset = 0; + private int patchedClassDefsOffset = 0; + private int patchedTypeListsOffset = 0; + private int patchedAnnotationItemsOffset = 0; + private int patchedAnnotationSetItemsOffset = 0; + private int patchedAnnotationSetRefListItemsOffset = 0; + private int patchedAnnotationsDirectoryItemsOffset = 0; + private int patchedDebugInfoItemsOffset = 0; + private int patchedCodeItemsOffset = 0; + private int patchedClassDataItemsOffset = 0; + private int patchedStringDataItemsOffset = 0; + private int patchedEncodedArrayItemsOffset = 0; + private int patchedMapListOffset = 0; + private int patchedDexSize = 0; + + public DexPatchGenerator(File oldDexFile, File newDexFile) throws IOException { + this(new Dex(oldDexFile), new Dex(newDexFile)); + } + + /** + * Notice: you should close inputstream manually. + */ + public DexPatchGenerator(File oldDexFile, InputStream newDexStream) throws IOException { + this(new Dex(oldDexFile), new Dex(newDexStream)); + } + + /** + * Notice: you should close inputstream manually. + */ + public DexPatchGenerator(InputStream oldDexStream, InputStream newDexStream) throws IOException { + this(new Dex(oldDexStream), new Dex(newDexStream)); + } + + public DexPatchGenerator(Dex oldDex, Dex newDex) { + this.oldDex = oldDex; + this.newDex = newDex; + + IndexMap oldToNewIndexMap = new IndexMap(); + IndexMap oldToPatchedIndexMap = new IndexMap(); + IndexMap newToPatchedIndexMap = new IndexMap(); + IndexMap selfIndexMapForSkip = new IndexMap(); + + additionalRemovingClassPatternSet = new HashSet<>(); + + this.stringDataSectionDiffAlg = new StringDataSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.typeIdSectionDiffAlg = new TypeIdSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.protoIdSectionDiffAlg = new ProtoIdSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.fieldIdSectionDiffAlg = new FieldIdSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.methodIdSectionDiffAlg = new MethodIdSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.classDefSectionDiffAlg = new ClassDefSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.typeListSectionDiffAlg = new TypeListSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.annotationSetRefListSectionDiffAlg = new AnnotationSetRefListSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.annotationSetSectionDiffAlg = new AnnotationSetSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.classDataSectionDiffAlg = new ClassDataSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.codeSectionDiffAlg = new CodeSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.debugInfoSectionDiffAlg = new DebugInfoItemSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.annotationSectionDiffAlg = new AnnotationSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.encodedArraySectionDiffAlg = new StaticValueSectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + this.annotationsDirectorySectionDiffAlg = new AnnotationsDirectorySectionDiffAlgorithm( + oldDex, newDex, + oldToNewIndexMap, + oldToPatchedIndexMap, + newToPatchedIndexMap, + selfIndexMapForSkip + ); + } + + public void addAdditionalRemovingClassPattern(String pattern) { + this.additionalRemovingClassPatternSet.add( + PatternUtils.dotClassNamePatternToDescriptorRegEx(pattern) + ); + } + + public void setAdditionalRemovingClassPatterns(Collection patterns) { + for (String pattern : patterns) { + this.additionalRemovingClassPatternSet.add( + PatternUtils.dotClassNamePatternToDescriptorRegEx(pattern) + ); + } + } + + public void clearAdditionalRemovingClassPatterns() { + this.additionalRemovingClassPatternSet.clear(); + } + + public void setLogger(DexPatcherLogger.IDexPatcherLogger logger) { + this.logger.setLoggerImpl(logger); + } + + public void executeAndSaveTo(File file) throws IOException { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + executeAndSaveTo(os); + } finally { + if (os != null) { + try { + os.close(); + } catch (Exception e) { + // ignored. + } + } + } + } + + public void executeAndSaveTo(OutputStream out) throws IOException { + // Firstly, collect information of items we want to remove additionally + // in new dex and set them to corresponding diff algorithm implementations. + Pattern[] classNamePatterns = new Pattern[this.additionalRemovingClassPatternSet.size()]; + int classNamePatternCount = 0; + for (String regExStr : this.additionalRemovingClassPatternSet) { + classNamePatterns[classNamePatternCount++] = Pattern.compile(regExStr); + } + + List typeIdOfClassDefsToRemove = new ArrayList<>(classNamePatternCount); + List offsetOfClassDatasToRemove = new ArrayList<>(classNamePatternCount); + for (ClassDef classDef : this.newDex.classDefs()) { + String typeName = this.newDex.typeNames().get(classDef.typeIndex); + for (Pattern pattern : classNamePatterns) { + if (pattern.matcher(typeName).matches()) { + typeIdOfClassDefsToRemove.add(classDef.typeIndex); + offsetOfClassDatasToRemove.add(classDef.classDataOffset); + break; + } + } + } + + ((ClassDefSectionDiffAlgorithm) this.classDefSectionDiffAlg) + .setTypeIdOfClassDefsToRemove(typeIdOfClassDefsToRemove); + ((ClassDataSectionDiffAlgorithm) this.classDataSectionDiffAlg) + .setOffsetOfClassDatasToRemove(offsetOfClassDatasToRemove); + + // Then, run diff algorithms according to sections' dependencies. + + // Use size calculated by algorithms above or from dex file definition to + // calculate sections' offset and patched dex size. + + // Calculate header and id sections size, so that we can work out + // the base offset of typeLists Section. + int patchedheaderSize = SizeOf.HEADER_ITEM; + int patchedStringIdsSize = newDex.getTableOfContents().stringIds.size * SizeOf.STRING_ID_ITEM; + int patchedTypeIdsSize = newDex.getTableOfContents().typeIds.size * SizeOf.TYPE_ID_ITEM; + + // Although simulatePatchOperation can calculate this value, since protoIds section + // depends on typeLists section, we can't run protoIds Section's simulatePatchOperation + // method so far. Instead we calculate protoIds section's size using information in newDex + // directly. + int patchedProtoIdsSize = newDex.getTableOfContents().protoIds.size * SizeOf.PROTO_ID_ITEM; + + int patchedFieldIdsSize = newDex.getTableOfContents().fieldIds.size * SizeOf.MEMBER_ID_ITEM; + int patchedMethodIdsSize = newDex.getTableOfContents().methodIds.size * SizeOf.MEMBER_ID_ITEM; + int patchedClassDefsSize = newDex.getTableOfContents().classDefs.size * SizeOf.CLASS_DEF_ITEM; + + int patchedIdSectionSize = + patchedStringIdsSize + + patchedTypeIdsSize + + patchedProtoIdsSize + + patchedFieldIdsSize + + patchedMethodIdsSize + + patchedClassDefsSize; + + this.patchedHeaderOffset = 0; + + // The diff works on each sections obey such procedure: + // 1. Execute diff algorithms to calculate indices of items we need to add, del and replace. + // 2. Execute patch algorithm simulation to calculate indices and offsets mappings that is + // necessary to next section's diff works. + + // Immediately do the patch simulation so that we can know: + // 1. Indices and offsets mapping between old dex and patched dex. + // 2. Indices and offsets mapping between new dex and patched dex. + // These information will be used to do next diff works. + this.patchedStringIdsOffset = patchedHeaderOffset + patchedheaderSize; + if (this.oldDex.getTableOfContents().stringIds.isElementFourByteAligned) { + this.patchedStringIdsOffset + = SizeOf.roundToTimesOfFour(this.patchedStringIdsOffset); + } + this.stringDataSectionDiffAlg.execute(); + this.patchedStringDataItemsOffset = patchedheaderSize + patchedIdSectionSize; + if (this.oldDex.getTableOfContents().stringDatas.isElementFourByteAligned) { + this.patchedStringDataItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedStringDataItemsOffset); + } + this.stringDataSectionDiffAlg.simulatePatchOperation(this.patchedStringDataItemsOffset); + + this.typeIdSectionDiffAlg.execute(); + this.patchedTypeIdsOffset = this.patchedStringIdsOffset + patchedStringIdsSize; + if (this.oldDex.getTableOfContents().typeIds.isElementFourByteAligned) { + this.patchedTypeIdsOffset + = SizeOf.roundToTimesOfFour(this.patchedTypeIdsOffset); + } + this.typeIdSectionDiffAlg.simulatePatchOperation(this.patchedTypeIdsOffset); + + this.typeListSectionDiffAlg.execute(); + this.patchedTypeListsOffset + = patchedheaderSize + + patchedIdSectionSize + + this.stringDataSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().typeLists.isElementFourByteAligned) { + this.patchedTypeListsOffset + = SizeOf.roundToTimesOfFour(this.patchedTypeListsOffset); + } + this.typeListSectionDiffAlg.simulatePatchOperation(this.patchedTypeListsOffset); + + this.protoIdSectionDiffAlg.execute(); + this.patchedProtoIdsOffset = this.patchedTypeIdsOffset + patchedTypeIdsSize; + if (this.oldDex.getTableOfContents().protoIds.isElementFourByteAligned) { + this.patchedProtoIdsOffset = SizeOf.roundToTimesOfFour(this.patchedProtoIdsOffset); + } + this.protoIdSectionDiffAlg.simulatePatchOperation(this.patchedProtoIdsOffset); + + this.fieldIdSectionDiffAlg.execute(); + this.patchedFieldIdsOffset = this.patchedProtoIdsOffset + patchedProtoIdsSize; + if (this.oldDex.getTableOfContents().fieldIds.isElementFourByteAligned) { + this.patchedFieldIdsOffset = SizeOf.roundToTimesOfFour(this.patchedFieldIdsOffset); + } + this.fieldIdSectionDiffAlg.simulatePatchOperation(this.patchedFieldIdsOffset); + + this.methodIdSectionDiffAlg.execute(); + this.patchedMethodIdsOffset = this.patchedFieldIdsOffset + patchedFieldIdsSize; + if (this.oldDex.getTableOfContents().methodIds.isElementFourByteAligned) { + this.patchedMethodIdsOffset = SizeOf.roundToTimesOfFour(this.patchedMethodIdsOffset); + } + this.methodIdSectionDiffAlg.simulatePatchOperation(this.patchedMethodIdsOffset); + + this.annotationSectionDiffAlg.execute(); + this.patchedAnnotationItemsOffset + = this.patchedTypeListsOffset + + this.typeListSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().annotations.isElementFourByteAligned) { + this.patchedAnnotationItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedAnnotationItemsOffset); + } + this.annotationSectionDiffAlg.simulatePatchOperation(this.patchedAnnotationItemsOffset); + + this.annotationSetSectionDiffAlg.execute(); + this.patchedAnnotationSetItemsOffset + = this.patchedAnnotationItemsOffset + + this.annotationSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().annotationSets.isElementFourByteAligned) { + this.patchedAnnotationSetItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedAnnotationSetItemsOffset); + } + this.annotationSetSectionDiffAlg.simulatePatchOperation( + this.patchedAnnotationSetItemsOffset + ); + + this.annotationSetRefListSectionDiffAlg.execute(); + this.patchedAnnotationSetRefListItemsOffset + = this.patchedAnnotationSetItemsOffset + + this.annotationSetSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().annotationSetRefLists.isElementFourByteAligned) { + this.patchedAnnotationSetRefListItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedAnnotationSetRefListItemsOffset); + } + this.annotationSetRefListSectionDiffAlg.simulatePatchOperation( + this.patchedAnnotationSetRefListItemsOffset + ); + + this.annotationsDirectorySectionDiffAlg.execute(); + this.patchedAnnotationsDirectoryItemsOffset + = this.patchedAnnotationSetRefListItemsOffset + + this.annotationSetRefListSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().annotationsDirectories.isElementFourByteAligned) { + this.patchedAnnotationsDirectoryItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedAnnotationsDirectoryItemsOffset); + } + this.annotationsDirectorySectionDiffAlg.simulatePatchOperation( + this.patchedAnnotationsDirectoryItemsOffset + ); + + this.debugInfoSectionDiffAlg.execute(); + this.patchedDebugInfoItemsOffset + = this.patchedAnnotationsDirectoryItemsOffset + + this.annotationsDirectorySectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().debugInfos.isElementFourByteAligned) { + this.patchedDebugInfoItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedDebugInfoItemsOffset); + } + this.debugInfoSectionDiffAlg.simulatePatchOperation(this.patchedDebugInfoItemsOffset); + + this.codeSectionDiffAlg.execute(); + this.patchedCodeItemsOffset + = this.patchedDebugInfoItemsOffset + + this.debugInfoSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().codes.isElementFourByteAligned) { + this.patchedCodeItemsOffset = SizeOf.roundToTimesOfFour(this.patchedCodeItemsOffset); + } + this.codeSectionDiffAlg.simulatePatchOperation(this.patchedCodeItemsOffset); + + this.classDataSectionDiffAlg.execute(); + this.patchedClassDataItemsOffset + = this.patchedCodeItemsOffset + + this.codeSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().classDatas.isElementFourByteAligned) { + this.patchedClassDataItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedClassDataItemsOffset); + } + this.classDataSectionDiffAlg.simulatePatchOperation(this.patchedClassDataItemsOffset); + + this.encodedArraySectionDiffAlg.execute(); + this.patchedEncodedArrayItemsOffset + = this.patchedClassDataItemsOffset + + this.classDataSectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().encodedArrays.isElementFourByteAligned) { + this.patchedEncodedArrayItemsOffset + = SizeOf.roundToTimesOfFour(this.patchedEncodedArrayItemsOffset); + } + this.encodedArraySectionDiffAlg.simulatePatchOperation(this.patchedEncodedArrayItemsOffset); + + this.classDefSectionDiffAlg.execute(); + this.patchedClassDefsOffset = this.patchedMethodIdsOffset + patchedMethodIdsSize; + if (this.oldDex.getTableOfContents().classDefs.isElementFourByteAligned) { + this.patchedClassDefsOffset = SizeOf.roundToTimesOfFour(this.patchedClassDefsOffset); + } + + // Calculate any values we still know nothing about them. + this.patchedMapListOffset + = this.patchedEncodedArrayItemsOffset + + this.encodedArraySectionDiffAlg.getPatchedSectionSize(); + if (this.oldDex.getTableOfContents().mapList.isElementFourByteAligned) { + this.patchedMapListOffset = SizeOf.roundToTimesOfFour(this.patchedMapListOffset); + } + int patchedMapListSize = newDex.getTableOfContents().mapList.byteCount; + + this.patchedDexSize + = this.patchedMapListOffset + + patchedMapListSize; + + // Finally, write results to patch file. + writeResultToStream(out); + } + + private void writeResultToStream(OutputStream os) throws IOException { + DexDataBuffer buffer = new DexDataBuffer(); + buffer.write(DexPatchFile.MAGIC); + buffer.writeShort(DexPatchFile.CURRENT_VERSION); + buffer.writeInt(this.patchedDexSize); + // we will return here to write firstChunkOffset later. + int posOfFirstChunkOffsetField = buffer.position(); + buffer.writeInt(0); + buffer.writeInt(this.patchedStringIdsOffset); + buffer.writeInt(this.patchedTypeIdsOffset); + buffer.writeInt(this.patchedProtoIdsOffset); + buffer.writeInt(this.patchedFieldIdsOffset); + buffer.writeInt(this.patchedMethodIdsOffset); + buffer.writeInt(this.patchedClassDefsOffset); + buffer.writeInt(this.patchedMapListOffset); + buffer.writeInt(this.patchedTypeListsOffset); + buffer.writeInt(this.patchedAnnotationSetRefListItemsOffset); + buffer.writeInt(this.patchedAnnotationSetItemsOffset); + buffer.writeInt(this.patchedClassDataItemsOffset); + buffer.writeInt(this.patchedCodeItemsOffset); + buffer.writeInt(this.patchedStringDataItemsOffset); + buffer.writeInt(this.patchedDebugInfoItemsOffset); + buffer.writeInt(this.patchedAnnotationItemsOffset); + buffer.writeInt(this.patchedEncodedArrayItemsOffset); + buffer.writeInt(this.patchedAnnotationsDirectoryItemsOffset); + buffer.write(this.oldDex.computeSignature(false)); + int firstChunkOffset = buffer.position(); + buffer.position(posOfFirstChunkOffsetField); + buffer.writeInt(firstChunkOffset); + buffer.position(firstChunkOffset); + + new PatchOperationsWriter(this.stringDataSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, StringData item) { + buffer.writeStringData(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.typeIdSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, Integer item) { + buffer.writeInt(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.typeListSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, TypeList item) { + buffer.writeTypeList(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.protoIdSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, ProtoId item) { + buffer.writeProtoId(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.fieldIdSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, FieldId item) { + buffer.writeFieldId(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.methodIdSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, MethodId item) { + buffer.writeMethodId(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.annotationSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, Annotation item) { + buffer.writeAnnotation(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.annotationSetSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, AnnotationSet item) { + buffer.writeAnnotationSet(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.annotationSetRefListSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, AnnotationSetRefList item) { + buffer.writeAnnotationSetRefList(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.annotationsDirectorySectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, AnnotationsDirectory item) { + buffer.writeAnnotationsDirectory(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.debugInfoSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, DebugInfoItem item) { + buffer.writeDebugInfoItem(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.codeSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, Code item) { + buffer.writeCode(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.classDataSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, ClassData item) { + buffer.writeClassData(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.encodedArraySectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, EncodedValue item) { + buffer.writeEncodedArray(item); + } + }.writeToBuffer(buffer); + + new PatchOperationsWriter(this.classDefSectionDiffAlg.getPatchOperationList()) { + @Override + protected void writeItem(DexDataBuffer buffer, ClassDef item) { + buffer.writeClassDef(item); + } + }.writeToBuffer(buffer); + + byte[] bufferData = buffer.array(); + os.write(bufferData); + os.flush(); + } + + private abstract class PatchOperationsWriter { + private final List> patchOperationList; + + PatchOperationsWriter(List> patchOperationList) { + this.patchOperationList = patchOperationList; + } + + protected abstract void writeItem(DexDataBuffer buffer, T item); + + public final void writeToBuffer(DexDataBuffer buffer) { + List delOpIndexList = new ArrayList<>(patchOperationList.size()); + List addOpIndexList = new ArrayList<>(patchOperationList.size()); + List replaceOpIndexList = new ArrayList<>(patchOperationList.size()); + List newItemList = new ArrayList<>(patchOperationList.size()); + + for (PatchOperation patchOperation : patchOperationList) { + switch (patchOperation.op) { + case PatchOperation.OP_DEL: { + delOpIndexList.add(patchOperation.index); + break; + } + case PatchOperation.OP_ADD: { + addOpIndexList.add(patchOperation.index); + newItemList.add(patchOperation.newItem); + break; + } + case PatchOperation.OP_REPLACE: { + replaceOpIndexList.add(patchOperation.index); + newItemList.add(patchOperation.newItem); + break; + } + } + } + + buffer.writeUleb128(delOpIndexList.size()); + int lastIndex = 0; + for (Integer index : delOpIndexList) { + buffer.writeSleb128(index - lastIndex); + lastIndex = index; + } + + buffer.writeUleb128(addOpIndexList.size()); + lastIndex = 0; + for (Integer index : addOpIndexList) { + buffer.writeSleb128(index - lastIndex); + lastIndex = index; + } + + buffer.writeUleb128(replaceOpIndexList.size()); + lastIndex = 0; + for (Integer index : replaceOpIndexList) { + buffer.writeSleb128(index - lastIndex); + lastIndex = index; + } + + for (T newItem : newItemList) { + writeItem(buffer, newItem); + } + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSectionDiffAlgorithm.java new file mode 100644 index 00000000..3d07d29e --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class AnnotationSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public AnnotationSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotations; + } + + @Override + protected Annotation nextItem(DexDataBuffer section) { + return section.readAnnotation(); + } + + @Override + protected int getItemSize(Annotation item) { + return item.byteCountInDex(); + } + + @Override + protected Annotation adjustItem(IndexMap indexMap, Annotation item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetRefListSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetRefListSectionDiffAlgorithm.java new file mode 100644 index 00000000..b8561637 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetRefListSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class AnnotationSetRefListSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public AnnotationSetRefListSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationSetRefLists; + } + + @Override + protected AnnotationSetRefList nextItem(DexDataBuffer section) { + return section.readAnnotationSetRefList(); + } + + @Override + protected int getItemSize(AnnotationSetRefList item) { + return item.byteCountInDex(); + } + + @Override + protected AnnotationSetRefList adjustItem(IndexMap indexMap, AnnotationSetRefList item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationSetRefListOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationSetRefListDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetSectionDiffAlgorithm.java new file mode 100644 index 00000000..98f02533 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class AnnotationSetSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public AnnotationSetSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationSets; + } + + @Override + protected AnnotationSet nextItem(DexDataBuffer section) { + return section.readAnnotationSet(); + } + + @Override + protected int getItemSize(AnnotationSet item) { + return item.byteCountInDex(); + } + + @Override + protected AnnotationSet adjustItem(IndexMap indexMap, AnnotationSet item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationSetOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationSetDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationsDirectorySectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationsDirectorySectionDiffAlgorithm.java new file mode 100644 index 00000000..a72a15ca --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationsDirectorySectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class AnnotationsDirectorySectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public AnnotationsDirectorySectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationsDirectories; + } + + @Override + protected AnnotationsDirectory nextItem(DexDataBuffer section) { + return section.readAnnotationsDirectory(); + } + + @Override + protected int getItemSize(AnnotationsDirectory item) { + return item.byteCountInDex(); + } + + @Override + protected AnnotationsDirectory adjustItem(IndexMap indexMap, AnnotationsDirectory item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationsDirectoryOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationsDirectoryDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDataSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDataSectionDiffAlgorithm.java new file mode 100644 index 00000000..addf7b0d --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDataSectionDiffAlgorithm.java @@ -0,0 +1,91 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class ClassDataSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + private Set offsetOfClassDataToRemoveSet = new HashSet<>(); + + public ClassDataSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + public void setOffsetOfClassDatasToRemove(Collection offsetOfClassDatasToRemove) { + this.offsetOfClassDataToRemoveSet.clear(); + this.offsetOfClassDataToRemoveSet.addAll(offsetOfClassDatasToRemove); + } + + public void clearTypeIdOfClassDefsToRemove() { + this.offsetOfClassDataToRemoveSet.clear(); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().classDatas; + } + + @Override + protected ClassData nextItem(DexDataBuffer section) { + return section.readClassData(); + } + + @Override + protected int getItemSize(ClassData item) { + return item.byteCountInDex(); + } + + @Override + protected ClassData adjustItem(IndexMap indexMap, ClassData item) { + return indexMap.adjust(item); + } + + @Override + public int getPatchedSectionSize() { + // assume each uleb128 field's length may be inflate by 2 bytes. + return super.getPatchedSectionSize() + newDex.getTableOfContents().classDatas.size * SizeOf.USHORT; + } + + @Override + protected boolean shouldSkipInNewDex(ClassData newItem) { + return this.offsetOfClassDataToRemoveSet.contains(newItem.off); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapClassDataOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markClassDataDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDefSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDefSectionDiffAlgorithm.java new file mode 100644 index 00000000..ebb558c7 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDefSectionDiffAlgorithm.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class ClassDefSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + private Set typeIdOfClassDefToRemoveSet = new HashSet<>(); + + public ClassDefSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + public void setTypeIdOfClassDefsToRemove(Collection typeIdOfClassDefsToRemove) { + this.typeIdOfClassDefToRemoveSet.clear(); + this.typeIdOfClassDefToRemoveSet.addAll(typeIdOfClassDefsToRemove); + } + + public void clearTypeIdOfClassDefsToRemove() { + this.typeIdOfClassDefToRemoveSet.clear(); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().classDefs; + } + + @Override + protected ClassDef nextItem(DexDataBuffer section) { + return section.readClassDef(); + } + + @Override + protected boolean shouldSkipInNewDex(ClassDef newItem) { + return this.typeIdOfClassDefToRemoveSet.contains(newItem.typeIndex); + } + + @Override + protected int getItemSize(ClassDef item) { + return SizeOf.CLASS_DEF_ITEM; + } + + @Override + protected ClassDef adjustItem(IndexMap indexMap, ClassDef item) { + return indexMap.adjust(item); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/CodeSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/CodeSectionDiffAlgorithm.java new file mode 100644 index 00000000..2672f9ca --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/CodeSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class CodeSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public CodeSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().codes; + } + + @Override + protected Code nextItem(DexDataBuffer section) { + return section.readCode(); + } + + @Override + protected int getItemSize(Code item) { + return item.byteCountInDex(); + } + + @Override + protected Code adjustItem(IndexMap indexMap, Code item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapCodeOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markCodeDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DebugInfoItemSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DebugInfoItemSectionDiffAlgorithm.java new file mode 100644 index 00000000..5c329203 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DebugInfoItemSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class DebugInfoItemSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public DebugInfoItemSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().debugInfos; + } + + @Override + protected DebugInfoItem nextItem(DexDataBuffer section) { + return section.readDebugInfoItem(); + } + + @Override + protected int getItemSize(DebugInfoItem item) { + return item.byteCountInDex(); + } + + @Override + protected DebugInfoItem adjustItem(IndexMap indexMap, DebugInfoItem item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapDebugInfoItemOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markDebugInfoItemDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DexSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DexSectionDiffAlgorithm.java new file mode 100644 index 00000000..9bf57a8e --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DexSectionDiffAlgorithm.java @@ -0,0 +1,450 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TableOfContents.Section.Item; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dex.util.CompareUtils; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.PatchOperation; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Created by tangyinsheng on 2016/6/29. + */ +public abstract class DexSectionDiffAlgorithm> { + private static final AbstractMap.SimpleEntry[] EMPTY_ENTRY_ARRAY = new AbstractMap.SimpleEntry[0]; + protected final Dex oldDex; + protected final Dex newDex; + /** + * IndexMap for mapping items between old dex and new dex. + * e.g. item.oldIndex => item.newIndex + */ + private final IndexMap oldToNewIndexMap; + /** + * IndexMap for mapping items between old dex and patched dex. + * e.g. item.oldIndex => item.patchedIndex + */ + private final IndexMap oldToPatchedIndexMap; + /** + * IndexMap for mapping items between new dex and patched dex. + * e.g. item.newIndex => item.newIndexInPatchedDex + */ + private final IndexMap newToPatchedIndexMap; + /** + * IndexMap for mapping items in new dex when skip items. + */ + private final IndexMap selfIndexMapForSkip; + private final List> patchOperationList; + private final Map> indexToDelOperationMap = new HashMap<>(); + private final Map> indexToAddOperationMap = new HashMap<>(); + private final Map> indexToReplaceOperationMap = new HashMap<>(); + private final Map oldIndexToNewIndexMap = new HashMap<>(); + private final Map oldOffsetToNewOffsetMap = new HashMap<>(); + private int patchedSectionSize; + private Comparator> comparatorForItemDiff = new Comparator>() { + @Override + public int compare(AbstractMap.SimpleEntry o1, AbstractMap.SimpleEntry o2) { + return o1.getValue().compareTo(o2.getValue()); + } + }; + private Comparator> comparatorForPatchOperationOpt = new Comparator>() { + @Override + public int compare(PatchOperation o1, PatchOperation o2) { + if (o1.index != o2.index) { + return CompareUtils.sCompare(o1.index, o2.index); + } + int o1OrderId; + switch (o1.op) { + case PatchOperation.OP_DEL: + o1OrderId = 0; + break; + case PatchOperation.OP_ADD: + o1OrderId = 1; + break; + case PatchOperation.OP_REPLACE: + o1OrderId = 2; + break; + default: + throw new IllegalStateException("unexpected patch operation code: " + o1.op); + } + int o2OrderId; + switch (o2.op) { + case PatchOperation.OP_DEL: + o2OrderId = 0; + break; + case PatchOperation.OP_ADD: + o2OrderId = 1; + break; + case PatchOperation.OP_REPLACE: + o2OrderId = 2; + break; + default: + throw new IllegalStateException("unexpected patch operation code: " + o2.op); + } + return CompareUtils.sCompare(o1OrderId, o2OrderId); + } + }; + private AbstractMap.SimpleEntry[] adjustedOldIndexedItemsWithOrigOrder = null; + private int oldItemCount = 0; + private int newItemCount = 0; + + public DexSectionDiffAlgorithm( + Dex oldDex, + Dex newDex, + IndexMap oldToNewIndexMap, + IndexMap oldToPatchedIndexMap, + IndexMap newToPatchedIndexMap, + IndexMap selfIndexMapForSkip + ) { + this.oldDex = oldDex; + this.newDex = newDex; + this.oldToNewIndexMap = oldToNewIndexMap; + this.oldToPatchedIndexMap = oldToPatchedIndexMap; + this.newToPatchedIndexMap = newToPatchedIndexMap; + this.selfIndexMapForSkip = selfIndexMapForSkip; + this.patchOperationList = new ArrayList<>(); + this.patchedSectionSize = 0; + } + + public List> getPatchOperationList() { + return this.patchOperationList; + } + + public int getPatchedSectionSize() { + return this.patchedSectionSize; + } + + /** + * Get {@code Section} in {@code TableOfContents}. + */ + protected abstract TableOfContents.Section getTocSection(Dex dex); + + /** + * Get next item in {@code section}. + */ + protected abstract T nextItem(DexDataBuffer section); + + /** + * Get item size. + */ + protected abstract int getItemSize(T item); + + /** + * Adjust {@code item} using specific {@code indexMap} + */ + protected T adjustItem(IndexMap indexMap, T item) { + return item; + } + + /** + * Indicate if {@code item} should be skipped in new dex. + */ + protected boolean shouldSkipInNewDex(T newItem) { + return false; + } + + /** + * Update index or offset mapping in {@code indexMap}. + */ + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + // Should override by subclass if needed. + } + + /** + * Mark deleted index or offset in {@code indexMap}. + */ + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + // Should override by subclass if needed. + } + + /** + * Adapter method for item's offset fetching, if an item is not + * inherited from {@code Item} (which means it is a simple item in dex section + * that doesn't need multiple members to describe), this method + * return {@code index} instead. + */ + private int getItemOffsetOrIndex(int index, T item) { + if (item instanceof Item) { + return ((Item) item).off; + } else { + return index; + } + } + + @SuppressWarnings("unchecked,NewApi") + private AbstractMap.SimpleEntry[] collectSectionItems(Dex dex, boolean isOldDex) { + TableOfContents.Section tocSec = getTocSection(dex); + if (!tocSec.exists()) { + return EMPTY_ENTRY_ARRAY; + } + Dex.Section dexSec = dex.openSection(tocSec); + int itemCount = tocSec.size; + List> result = new ArrayList<>(itemCount); + if (isOldDex) { + for (int i = 0; i < itemCount; ++i) { + T nextItem = nextItem(dexSec); + T adjustedItem = adjustItem(oldToPatchedIndexMap, nextItem); + result.add(new AbstractMap.SimpleEntry<>(i, adjustedItem)); + } + } else { + int i = 0; + while (i < itemCount) { + T nextItem = nextItem(dexSec); + int indexBeforeSkip = i; + int offsetBeforeSkip = getItemOffsetOrIndex(indexBeforeSkip, nextItem); + int indexAfterSkip = indexBeforeSkip; + while (indexAfterSkip < itemCount && shouldSkipInNewDex(nextItem)) { + if (indexAfterSkip + 1 >= itemCount) { + // after skipping last item, nextItem will be null. + nextItem = null; + } else { + nextItem = nextItem(dexSec); + } + ++indexAfterSkip; + } + if (nextItem != null) { + int offsetAfterSkip = getItemOffsetOrIndex(indexAfterSkip, nextItem); + T adjustedItem = adjustItem(newToPatchedIndexMap, adjustItem(selfIndexMapForSkip, nextItem)); + int currentOutIndex = result.size(); + result.add(new AbstractMap.SimpleEntry<>(currentOutIndex, adjustedItem)); + updateIndexOrOffset(selfIndexMapForSkip, indexBeforeSkip, offsetBeforeSkip, indexAfterSkip, offsetAfterSkip); + } + i = indexAfterSkip; + ++i; + } + } + return result.toArray(new AbstractMap.SimpleEntry[0]); + } + + public void execute() { + this.patchOperationList.clear(); + + this.adjustedOldIndexedItemsWithOrigOrder = collectSectionItems(this.oldDex, true); + this.oldItemCount = this.adjustedOldIndexedItemsWithOrigOrder.length; + + AbstractMap.SimpleEntry[] adjustedOldIndexedItems = new AbstractMap.SimpleEntry[this.oldItemCount]; + System.arraycopy(this.adjustedOldIndexedItemsWithOrigOrder, 0, adjustedOldIndexedItems, 0, this.oldItemCount); + Arrays.sort(adjustedOldIndexedItems, this.comparatorForItemDiff); + + AbstractMap.SimpleEntry[] adjustedNewIndexedItems = collectSectionItems(this.newDex, false); + this.newItemCount = adjustedNewIndexedItems.length; + Arrays.sort(adjustedNewIndexedItems, this.comparatorForItemDiff); + + int oldCursor = 0; + int newCursor = 0; + while (oldCursor < this.oldItemCount || newCursor < this.newItemCount) { + if (oldCursor >= this.oldItemCount) { + // rest item are all newItem. + while (newCursor < this.newItemCount) { + AbstractMap.SimpleEntry newIndexedItem = adjustedNewIndexedItems[newCursor++]; + this.patchOperationList.add(new PatchOperation<>(PatchOperation.OP_ADD, newIndexedItem.getKey(), newIndexedItem.getValue())); + } + } else + if (newCursor >= newItemCount) { + // rest item are all oldItem. + while (oldCursor < oldItemCount) { + AbstractMap.SimpleEntry oldIndexedItem = adjustedOldIndexedItems[oldCursor++]; + int deletedIndex = oldIndexedItem.getKey(); + int deletedOffset = getItemOffsetOrIndex(deletedIndex, oldIndexedItem.getValue()); + this.patchOperationList.add(new PatchOperation(PatchOperation.OP_DEL, deletedIndex)); + markDeletedIndexOrOffset(this.oldToPatchedIndexMap, deletedIndex, deletedOffset); + } + } else { + AbstractMap.SimpleEntry oldIndexedItem = adjustedOldIndexedItems[oldCursor]; + AbstractMap.SimpleEntry newIndexedItem = adjustedNewIndexedItems[newCursor]; + int cmpRes = oldIndexedItem.getValue().compareTo(newIndexedItem.getValue()); + if (cmpRes < 0) { + int deletedIndex = oldIndexedItem.getKey(); + int deletedOffset = getItemOffsetOrIndex(deletedIndex, oldIndexedItem.getValue()); + this.patchOperationList.add(new PatchOperation(PatchOperation.OP_DEL, deletedIndex)); + markDeletedIndexOrOffset(this.oldToPatchedIndexMap, deletedIndex, deletedOffset); + ++oldCursor; + } else + if (cmpRes > 0) { + this.patchOperationList.add(new PatchOperation<>(PatchOperation.OP_ADD, newIndexedItem.getKey(), newIndexedItem.getValue())); + ++newCursor; + } else { + int oldIndex = oldIndexedItem.getKey(); + int newIndex = newIndexedItem.getKey(); + int oldOffset = getItemOffsetOrIndex(oldIndexedItem.getKey(), oldIndexedItem.getValue()); + int newOffset = getItemOffsetOrIndex(newIndexedItem.getKey(), newIndexedItem.getValue()); + + if (oldIndex != newIndex) { + this.oldIndexToNewIndexMap.put(oldIndex, newIndex); + } + + if (oldOffset != newOffset) { + this.oldOffsetToNewOffsetMap.put(oldOffset, newOffset); + } + + ++oldCursor; + ++newCursor; + } + } + } + + // So far all diff works are done. Then we perform some optimize works. + // detail: {OP_DEL idx} followed by {OP_ADD the_same_idx newItem} + // will be replaced by {OP_REPLACE idx newItem} + Collections.sort(this.patchOperationList, comparatorForPatchOperationOpt); + + Iterator> patchOperationIt = this.patchOperationList.iterator(); + PatchOperation prevPatchOperation = null; + while (patchOperationIt.hasNext()) { + PatchOperation patchOperation = patchOperationIt.next(); + if (prevPatchOperation != null + && prevPatchOperation.op == PatchOperation.OP_DEL + && patchOperation.op == PatchOperation.OP_ADD + ) { + if (prevPatchOperation.index == patchOperation.index) { + prevPatchOperation.op = PatchOperation.OP_REPLACE; + prevPatchOperation.newItem = patchOperation.newItem; + patchOperationIt.remove(); + prevPatchOperation = null; + } else { + prevPatchOperation = patchOperation; + } + } else { + prevPatchOperation = patchOperation; + } + } + + // Finally we record some information for the final calculations. + patchOperationIt = this.patchOperationList.iterator(); + while (patchOperationIt.hasNext()) { + PatchOperation patchOperation = patchOperationIt.next(); + switch (patchOperation.op) { + case PatchOperation.OP_DEL: { + indexToDelOperationMap.put(patchOperation.index, patchOperation); + break; + } + case PatchOperation.OP_ADD: { + indexToAddOperationMap.put(patchOperation.index, patchOperation); + break; + } + case PatchOperation.OP_REPLACE: { + indexToReplaceOperationMap.put(patchOperation.index, patchOperation); + break; + } + } + } + } + + public void simulatePatchOperation(int baseOffset) { + boolean isNeedToMakeAlign = getTocSection(this.oldDex).isElementFourByteAligned; + int oldIndex = 0; + int patchedIndex = 0; + int patchedOffset = baseOffset; + while (oldIndex < this.oldItemCount || patchedIndex < this.newItemCount) { + if (this.indexToAddOperationMap.containsKey(patchedIndex)) { + PatchOperation patchOperation = this.indexToAddOperationMap.get(patchedIndex); + if (isNeedToMakeAlign) { + patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); + } + T newItem = patchOperation.newItem; + int itemSize = getItemSize(newItem); + updateIndexOrOffset( + this.newToPatchedIndexMap, + 0, + getItemOffsetOrIndex(patchOperation.index, newItem), + 0, + patchedOffset + ); + ++patchedIndex; + patchedOffset += itemSize; + } else + if (this.indexToReplaceOperationMap.containsKey(patchedIndex)) { + PatchOperation patchOperation = this.indexToReplaceOperationMap.get(patchedIndex); + if (isNeedToMakeAlign) { + patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); + } + T newItem = patchOperation.newItem; + int itemSize = getItemSize(newItem); + updateIndexOrOffset( + this.newToPatchedIndexMap, + 0, + getItemOffsetOrIndex(patchOperation.index, newItem), + 0, + patchedOffset + ); + ++patchedIndex; + patchedOffset += itemSize; + } else + if (this.indexToDelOperationMap.containsKey(oldIndex)) { + ++oldIndex; + } else + if (this.indexToReplaceOperationMap.containsKey(oldIndex)) { + ++oldIndex; + } else + if (oldIndex < this.oldItemCount) { + if (isNeedToMakeAlign) { + patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); + } + + T oldItem = this.adjustedOldIndexedItemsWithOrigOrder[oldIndex].getValue(); + int itemSize = getItemSize(oldItem); + + int oldOffset = getItemOffsetOrIndex(oldIndex, oldItem); + + updateIndexOrOffset( + this.oldToPatchedIndexMap, + oldIndex, + oldOffset, + patchedIndex, + patchedOffset + ); + + int newIndex = oldIndex; + if (this.oldIndexToNewIndexMap.containsKey(oldIndex)) { + newIndex = this.oldIndexToNewIndexMap.get(oldIndex); + } + + int newOffset = oldOffset; + if (this.oldOffsetToNewOffsetMap.containsKey(oldOffset)) { + newOffset = this.oldOffsetToNewOffsetMap.get(oldOffset); + } + + updateIndexOrOffset( + this.newToPatchedIndexMap, + newIndex, + newOffset, + patchedIndex, + patchedOffset + ); + + ++oldIndex; + ++patchedIndex; + patchedOffset += itemSize; + } + } + + this.patchedSectionSize = SizeOf.roundToTimesOfFour(patchedOffset - baseOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/FieldIdSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/FieldIdSectionDiffAlgorithm.java new file mode 100644 index 00000000..f1751361 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/FieldIdSectionDiffAlgorithm.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class FieldIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public FieldIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().fieldIds; + } + + @Override + protected FieldId nextItem(DexDataBuffer section) { + return section.readFieldId(); + } + + @Override + protected int getItemSize(FieldId item) { + return SizeOf.MEMBER_ID_ITEM; + } + + @Override + protected FieldId adjustItem(IndexMap indexMap, FieldId item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapFieldIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markFieldIdDeleted(deletedIndex); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/MethodIdSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/MethodIdSectionDiffAlgorithm.java new file mode 100644 index 00000000..4456dacb --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/MethodIdSectionDiffAlgorithm.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class MethodIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public MethodIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().methodIds; + } + + @Override + protected MethodId nextItem(DexDataBuffer section) { + return section.readMethodId(); + } + + @Override + protected int getItemSize(MethodId item) { + return SizeOf.MEMBER_ID_ITEM; + } + + @Override + protected MethodId adjustItem(IndexMap indexMap, MethodId item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapMethodIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markMethodIdDeleted(deletedIndex); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ProtoIdSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ProtoIdSectionDiffAlgorithm.java new file mode 100644 index 00000000..fcec419b --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ProtoIdSectionDiffAlgorithm.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class ProtoIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public ProtoIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().protoIds; + } + + @Override + protected ProtoId nextItem(DexDataBuffer section) { + return section.readProtoId(); + } + + @Override + protected int getItemSize(ProtoId item) { + return SizeOf.PROTO_ID_ITEM; + } + + @Override + protected ProtoId adjustItem(IndexMap indexMap, ProtoId item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapProtoIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markProtoIdDeleted(deletedIndex); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StaticValueSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StaticValueSectionDiffAlgorithm.java new file mode 100644 index 00000000..51e7a223 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StaticValueSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class StaticValueSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public StaticValueSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().encodedArrays; + } + + @Override + protected EncodedValue nextItem(DexDataBuffer section) { + return section.readEncodedArray(); + } + + @Override + protected int getItemSize(EncodedValue item) { + return item.byteCountInDex(); + } + + @Override + protected EncodedValue adjustItem(IndexMap indexMap, EncodedValue item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapStaticValuesOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markStaticValuesDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StringDataSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StringDataSectionDiffAlgorithm.java new file mode 100644 index 00000000..e75a9c37 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StringDataSectionDiffAlgorithm.java @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class StringDataSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public StringDataSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().stringDatas; + } + + @Override + protected StringData nextItem(DexDataBuffer section) { + return section.readStringData(); + } + + @Override + protected int getItemSize(StringData item) { + return item.byteCountInDex(); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapStringIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markStringIdDeleted(deletedIndex); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeIdSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeIdSectionDiffAlgorithm.java new file mode 100644 index 00000000..e9b3dd75 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeIdSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class TypeIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public TypeIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().typeIds; + } + + @Override + protected Integer nextItem(DexDataBuffer section) { + return section.readInt(); + } + + @Override + protected int getItemSize(Integer item) { + return SizeOf.UINT; + } + + @Override + protected Integer adjustItem(IndexMap indexMap, Integer item) { + return indexMap.adjustStringIndex(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapTypeIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markTypeIdDeleted(deletedIndex); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeListSectionDiffAlgorithm.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeListSectionDiffAlgorithm.java new file mode 100644 index 00000000..2562600e --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeListSectionDiffAlgorithm.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.algorithms.diff; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class TypeListSectionDiffAlgorithm extends DexSectionDiffAlgorithm { + public TypeListSectionDiffAlgorithm(Dex oldDex, Dex newDex, IndexMap oldToNewIndexMap, IndexMap oldToPatchedIndexMap, IndexMap newToPatchedIndexMap, IndexMap selfIndexMapForSkip) { + super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().typeLists; + } + + @Override + protected TypeList nextItem(DexDataBuffer section) { + return section.readTypeList(); + } + + @Override + protected int getItemSize(TypeList item) { + return item.byteCountInDex(); + } + + @Override + protected TypeList adjustItem(IndexMap indexMap, TypeList item) { + return indexMap.adjust(item); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapTypeListOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markTypeListDeleted(deletedOffset); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/OffsetToIndexConverter.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/OffsetToIndexConverter.java new file mode 100644 index 00000000..80673a8a --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/OffsetToIndexConverter.java @@ -0,0 +1,187 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.util; + +import com.tencent.tinker.android.dex.Dex; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by tangyinsheng on 2016/9/11. + */ +public final class OffsetToIndexConverter { + private final Map typeListOffsetToIndexMap = new HashMap<>(); + private final Map classDataOffsetToIndexMap = new HashMap<>(); + private final Map encodedArrayOffsetToIndexMap = new HashMap<>(); + private final Map annotationOffsetToIndexMap = new HashMap<>(); + private final Map annotationSetOffsetToIndexMap = new HashMap<>(); + private final Map annotationSetRefListOffsetToIndexMap = new HashMap<>(); + private final Map annotationsDirectoryOffsetToIndexMap = new HashMap<>(); + private final Map codeOffsetToIndexMap = new HashMap<>(); + private final Map debugInfoItemOffsetToIndexMap = new HashMap<>(); + + public OffsetToIndexConverter(Dex dex) { + if (dex == null) { + throw new IllegalArgumentException("dex is null."); + } + + if (dex.getTableOfContents().typeLists.exists()) { + Dex.Section typeListSec = dex.openSection(dex.getTableOfContents().typeLists); + int typeListCount = dex.getTableOfContents().typeLists.size; + for (int i = 0; i < typeListCount; ++i) { + typeListOffsetToIndexMap.put(typeListSec.readTypeList().off, i); + } + } + + if (dex.getTableOfContents().classDatas.exists()) { + Dex.Section classDataSec = dex.openSection(dex.getTableOfContents().classDatas); + int classDataCount = dex.getTableOfContents().classDatas.size; + for (int i = 0; i < classDataCount; ++i) { + classDataOffsetToIndexMap.put(classDataSec.readClassData().off, i); + } + } + + if (dex.getTableOfContents().encodedArrays.exists()) { + Dex.Section encodedArraySec = dex.openSection(dex.getTableOfContents().encodedArrays); + int encodedArrayCount = dex.getTableOfContents().encodedArrays.size; + for (int i = 0; i < encodedArrayCount; ++i) { + encodedArrayOffsetToIndexMap.put(encodedArraySec.readEncodedArray().off, i); + } + } + + if (dex.getTableOfContents().annotations.exists()) { + Dex.Section annotationSec = dex.openSection(dex.getTableOfContents().annotations); + int annotationCount = dex.getTableOfContents().annotations.size; + for (int i = 0; i < annotationCount; ++i) { + annotationOffsetToIndexMap.put(annotationSec.readAnnotation().off, i); + } + } + + if (dex.getTableOfContents().annotationSets.exists()) { + Dex.Section annotationSetSec = dex.openSection(dex.getTableOfContents().annotationSets); + int annotationSetCount = dex.getTableOfContents().annotationSets.size; + for (int i = 0; i < annotationSetCount; ++i) { + annotationSetOffsetToIndexMap.put(annotationSetSec.readAnnotationSet().off, i); + } + } + + if (dex.getTableOfContents().annotationSetRefLists.exists()) { + Dex.Section annotationSetRefListSec = dex.openSection(dex.getTableOfContents().annotationSetRefLists); + int annotationSetRefListCount = dex.getTableOfContents().annotationSetRefLists.size; + for (int i = 0; i < annotationSetRefListCount; ++i) { + annotationSetRefListOffsetToIndexMap.put(annotationSetRefListSec.readAnnotationSetRefList().off, i); + } + } + + if (dex.getTableOfContents().annotationsDirectories.exists()) { + Dex.Section annotationsDirectorySec = dex.openSection(dex.getTableOfContents().annotationsDirectories); + int annotationsDirectoryCount = dex.getTableOfContents().annotationsDirectories.size; + for (int i = 0; i < annotationsDirectoryCount; ++i) { + annotationsDirectoryOffsetToIndexMap.put(annotationsDirectorySec.readAnnotationsDirectory().off, i); + } + } + + if (dex.getTableOfContents().codes.exists()) { + Dex.Section codeSec = dex.openSection(dex.getTableOfContents().codes); + int codeCount = dex.getTableOfContents().codes.size; + for (int i = 0; i < codeCount; ++i) { + codeOffsetToIndexMap.put(codeSec.readCode().off, i); + } + } + + if (dex.getTableOfContents().debugInfos.exists()) { + Dex.Section debugInfoItemSec = dex.openSection(dex.getTableOfContents().debugInfos); + int debugInfoItemCount = dex.getTableOfContents().debugInfos.size; + for (int i = 0; i < debugInfoItemCount; ++i) { + debugInfoItemOffsetToIndexMap.put(debugInfoItemSec.readDebugInfoItem().off, i); + } + } + } + + public int getTypeListIndexByOffset(int offset) { + if (typeListOffsetToIndexMap.containsKey(offset)) { + return typeListOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getClassDataIndexByOffset(int offset) { + if (classDataOffsetToIndexMap.containsKey(offset)) { + return classDataOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getEncodedArrayIndexByOffset(int offset) { + if (encodedArrayOffsetToIndexMap.containsKey(offset)) { + return encodedArrayOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getAnnotationIndexByOffset(int offset) { + if (annotationOffsetToIndexMap.containsKey(offset)) { + return annotationOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getAnnotationSetIndexByOffset(int offset) { + if (annotationSetOffsetToIndexMap.containsKey(offset)) { + return annotationSetOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getAnnotationSetRefListIndexByOffset(int offset) { + if (annotationSetRefListOffsetToIndexMap.containsKey(offset)) { + return annotationSetRefListOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getAnnotationsDirectoryIndexByOffset(int offset) { + if (annotationsDirectoryOffsetToIndexMap.containsKey(offset)) { + return annotationsDirectoryOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getCodeIndexByOffset(int offset) { + if (codeOffsetToIndexMap.containsKey(offset)) { + return codeOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } + + public int getDebugInfoItemIndexByOffset(int offset) { + if (debugInfoItemOffsetToIndexMap.containsKey(offset)) { + return debugInfoItemOffsetToIndexMap.get(offset); + } else { + throw new IllegalArgumentException("cannot find corresponding index of offset: " + offset); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/PatternUtils.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/PatternUtils.java new file mode 100644 index 00000000..988290c2 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/PatternUtils.java @@ -0,0 +1,89 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.util; + +/** + * Created by tangyinsheng on 2016/4/8. + */ +public class PatternUtils { + + public static String dotClassNamePatternToDescriptorRegEx(String dotPattern) { + if (dotPattern.startsWith("L") && dotPattern.endsWith(";") || dotPattern.startsWith("[")) { + return dotPattern.replace('.', '/').replace("[", "\\["); + } + + String descriptor = dotPattern.replace('.', '/'); + + StringBuilder sb = new StringBuilder(); + + int i; + for (i = dotPattern.length() - 1; i >= 1; i -= 2) { + char ch = dotPattern.charAt(i); + char prevCh = dotPattern.charAt(i - 1); + if (prevCh == '[' && ch == ']') { + sb.append("\\["); + } else { + break; + } + } + + descriptor = descriptor.substring(0, i + 1); + + if ("void".equals(descriptor)) { + descriptor = "V"; + sb.append(descriptor); + } else if ("boolean".equals(descriptor)) { + descriptor = "Z"; + sb.append(descriptor); + } else if ("byte".equals(descriptor)) { + descriptor = "B"; + sb.append(descriptor); + } else if ("short".equals(descriptor)) { + descriptor = "S"; + sb.append(descriptor); + } else if ("char".equals(descriptor)) { + descriptor = "C"; + sb.append(descriptor); + } else if ("int".equals(descriptor)) { + descriptor = "I"; + sb.append(descriptor); + } else if ("long".equals(descriptor)) { + descriptor = "J"; + sb.append(descriptor); + } else if ("float".equals(descriptor)) { + descriptor = "F"; + sb.append(descriptor); + } else if ("double".equals(descriptor)) { + descriptor = "D"; + sb.append(descriptor); + } else { + sb.append('L').append(descriptor); + + if (!descriptor.endsWith(";")) { + sb.append(';'); + } + } + + String regEx = sb.toString(); + regEx = regEx.replace("*", ".*"); + regEx = regEx.replace("?", ".?"); + regEx = regEx.replace("$", "\\$"); + regEx = '^' + regEx + '$'; + + return regEx; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/SmallDexPatchGenerator.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/SmallDexPatchGenerator.java new file mode 100644 index 00000000..dd805303 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/SmallDexPatchGenerator.java @@ -0,0 +1,1965 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.dexpatcher.util; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.DexException; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.EncodedValueReader; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.Leb128; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dex.util.ByteInput; +import com.tencent.tinker.android.dx.instruction.InstructionCodec; +import com.tencent.tinker.android.dx.instruction.InstructionReader; +import com.tencent.tinker.android.dx.instruction.InstructionVisitor; +import com.tencent.tinker.android.dx.instruction.ShortArrayCodeInput; +import com.tencent.tinker.android.dx.util.Hex; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.build.util.DexClassesComparator; +import com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; +import com.tencent.tinker.build.util.DexClassesComparator.DexGroup; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger.IDexPatcherLogger; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by tangyinsheng on 2016/8/8. + */ +public final class SmallDexPatchGenerator { + private static final String TAG = "SmallDexPatchGenerator"; + + private final List oldDexGroups = new ArrayList<>(); + private final List patchedDexGroups = new ArrayList<>(); + + private final Map> + patchedDexToCollectedStringIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedTypeIdIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedTypeListIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedProtoIdIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedFieldIdIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedMethodIdIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedAnnotationIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedAnnotationSetIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedAnnotationSetRefListIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedAnnotationsDirectoryIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedEncodedArrayIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedDebugInfoIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedCodeIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedClassDataIndicesMap = new HashMap<>(); + private final Map> + patchedDexToCollectedClassDefIndicesMap = new HashMap<>(); + + private final Map + patchedDexToSmallPatchedStringIdOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedTypeIdOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedProtoIdOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedFieldIdOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedMethodIdOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedClassDefOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedMapListOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedTypeListOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedAnnotationSetRefListOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedAnnotationSetOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedClassDataOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedCodeOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedStringDataOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedDebugInfoOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedAnnotationOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedEncodedArrayOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedAnnotationsDirectoryOffsetMap = new HashMap<>(); + private final Map + patchedDexToSmallPatchedDexSizeMap = new HashMap<>(); + + private final Set loaderClassPatterns = new HashSet<>(); + + private final DexPatcherLogger logger = new DexPatcherLogger(); + + public void addLoaderClassPattern(String pattern) { + this.loaderClassPatterns.add(pattern); + } + + public void setLoaderClassPatterns(Collection patterns) { + this.loaderClassPatterns.clear(); + this.loaderClassPatterns.addAll(patterns); + } + + public void clearLoaderClassPatterns() { + this.loaderClassPatterns.clear(); + } + + public void setLogger(IDexPatcherLogger logger) { + this.logger.setLoggerImpl(logger); + } + + public SmallDexPatchGenerator appendDexGroup(DexGroup oldDexGroup, DexGroup patchedDexGroup) { + if (oldDexGroup == null) { + throw new IllegalArgumentException("oldDexGroup is null."); + } + if (patchedDexGroup == null) { + throw new IllegalArgumentException("patchedDexGroup is null."); + } + + this.oldDexGroups.add(oldDexGroup); + this.patchedDexGroups.add(patchedDexGroup); + + // Build map between patched dex and old dex, which is used in next logic. + if (oldDexGroup.dexes.length != patchedDexGroup.dexes.length) { + throw new IllegalArgumentException( + "dex count in oldDexGroup is not matched to dex count in patchedDexGroup." + ); + } + + return this; + } + + public void executeAndSaveTo(File out) throws IOException { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(out)); + executeAndSaveTo(os); + } finally { + if (os != null) { + try { + os.close(); + } catch (Exception e) { + // ignored. + } + } + } + } + + public void executeAndSaveTo(OutputStream os) throws IOException { + int dexGroupCount = this.oldDexGroups.size(); + + // Collect all items that should be exist in small patched dex. + for (int i = 0; i < dexGroupCount; ++i) { + DexGroup oldDexGroup = oldDexGroups.get(i); + DexGroup patchedDexGroup = patchedDexGroups.get(i); + + collectItemIndicesFromDexGroup(oldDexGroup, patchedDexGroup); + calculateSmallPatchedSectionOffsets(oldDexGroup, patchedDexGroup); + } + + saveToStream(os); + } + + private void calculateSmallPatchedSectionOffsets( + DexGroup oldDexGroup, DexGroup patchedDexGroup + ) { + if (oldDexGroup.dexes.length != patchedDexGroup.dexes.length) { + throw new IllegalStateException("dex group contains different amount of dexes."); + } + int dexCount = oldDexGroup.dexes.length; + for (int dexId = 0; dexId < dexCount; ++dexId) { + Dex oldDex = oldDexGroup.dexes[dexId]; + Dex patchedDex = patchedDexGroup.dexes[dexId]; + + final String currOldDexSignStr = Hex.toHexString(oldDex.computeSignature(false)); + + IndexMap fullToSmallPatchIndexMap = new IndexMap(); + + // For calculating size of mapList soon. + // Initialize it to 2 means a dex must contains two sections: header + // and mapList. + int smallPatchedSectionCount = 2; + + // In next steps we do a bunch of simulations to calculate actual sizes of + // each section in small patched dex. + + // First, calculate header and id sections size, so that we can work out + // base offsets of data sections soon. + int smallPatchedHeaderSize = SizeOf.HEADER_ITEM; + int collectedStringIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedStringIndicesMap, patchedDex + ); + int smallPatchedStringIdsSize = collectedStringIndicesCount * SizeOf.STRING_ID_ITEM; + if (smallPatchedHeaderSize > 0) { + ++smallPatchedSectionCount; + } + int collectedTypeIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedTypeIdIndicesMap, patchedDex + ); + int smallPatchedTypeIdsSize = collectedTypeIndicesCount * SizeOf.TYPE_ID_ITEM; + if (smallPatchedTypeIdsSize > 0) { + ++smallPatchedSectionCount; + } + + // Although simulatePatchOperation can calculate this value, since protoIds section + // depends on typeLists section, we can't run protoIds Section's simulatePatchOperation + // method so far. Instead we calculate protoIds section's size using information we known + // directly. + int collectedProtoIdsIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedProtoIdIndicesMap, patchedDex + ); + int smallPatchedProtoIdsSize = collectedProtoIdsIndicesCount * SizeOf.PROTO_ID_ITEM; + if (smallPatchedProtoIdsSize > 0) { + ++smallPatchedSectionCount; + } + + int collectedFieldIdsIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedFieldIdIndicesMap, patchedDex + ); + int smallPatchedFieldIdsSize = collectedFieldIdsIndicesCount * SizeOf.MEMBER_ID_ITEM; + if (smallPatchedFieldIdsSize > 0) { + ++smallPatchedSectionCount; + } + int collectedMethodIdsIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedMethodIdIndicesMap, patchedDex + ); + int smallPatchedMethodIdsSize = collectedMethodIdsIndicesCount * SizeOf.MEMBER_ID_ITEM; + if (smallPatchedMethodIdsSize > 0) { + ++smallPatchedSectionCount; + } + int collectedClassDefsIndicesCount = getCollectedIndicesCountSafely( + patchedDexToCollectedClassDefIndicesMap, patchedDex + ); + int smallPatchedClassDefsSize = collectedClassDefsIndicesCount * SizeOf.CLASS_DEF_ITEM; + if (smallPatchedClassDefsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedIdSectionSize = + smallPatchedStringIdsSize + + smallPatchedTypeIdsSize + + smallPatchedProtoIdsSize + + smallPatchedFieldIdsSize + + smallPatchedMethodIdsSize + + smallPatchedClassDefsSize; + + int smallPatchedHeaderOffset = 0; + + int smallPatchedStringIdsOffset = smallPatchedHeaderOffset + smallPatchedHeaderSize; + if (oldDex.getTableOfContents().stringIds.isElementFourByteAligned) { + smallPatchedStringIdsOffset = SizeOf.roundToTimesOfFour(smallPatchedStringIdsOffset); + } + patchedDexToSmallPatchedStringIdOffsetMap.put(patchedDex, smallPatchedStringIdsOffset); + + int smallPatchedStringDatasOffset = smallPatchedHeaderSize + smallPatchedIdSectionSize; + if (oldDex.getTableOfContents().stringDatas.isElementFourByteAligned) { + smallPatchedStringDatasOffset + = SizeOf.roundToTimesOfFour(smallPatchedStringDatasOffset); + } + patchedDexToSmallPatchedStringDataOffsetMap.put(patchedDex, smallPatchedStringDatasOffset); + int smallPatchedStringDataItemsSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().stringDatas, + fullToSmallPatchIndexMap, + patchedDexToCollectedStringIndicesMap.get(patchedDex) + ).simulate(smallPatchedStringDatasOffset); + if (smallPatchedStringDataItemsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedTypeIdsOffset + = smallPatchedStringIdsOffset + smallPatchedStringIdsSize; + if (oldDex.getTableOfContents().typeIds.isElementFourByteAligned) { + smallPatchedTypeIdsOffset = SizeOf.roundToTimesOfFour(smallPatchedTypeIdsOffset); + } + patchedDexToSmallPatchedTypeIdOffsetMap.put(patchedDex, smallPatchedTypeIdsOffset); + + int smallPatchedTypeListsOffset + = smallPatchedHeaderSize + + smallPatchedIdSectionSize + + smallPatchedStringDataItemsSize; + if (oldDex.getTableOfContents().typeLists.isElementFourByteAligned) { + smallPatchedTypeListsOffset = SizeOf.roundToTimesOfFour(smallPatchedTypeListsOffset); + } + patchedDexToSmallPatchedTypeListOffsetMap.put( + patchedDex, smallPatchedTypeListsOffset + ); + int smallPatchedTypeListsSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().typeLists, + fullToSmallPatchIndexMap, + patchedDexToCollectedTypeListIndicesMap.get(patchedDex) + ).simulate(smallPatchedTypeListsOffset); + if (smallPatchedTypeListsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedProtoIdsOffset + = smallPatchedTypeIdsOffset + smallPatchedTypeIdsSize; + if (oldDex.getTableOfContents().protoIds.isElementFourByteAligned) { + smallPatchedProtoIdsOffset = SizeOf.roundToTimesOfFour(smallPatchedProtoIdsOffset); + } + patchedDexToSmallPatchedProtoIdOffsetMap.put( + patchedDex, smallPatchedProtoIdsOffset + ); + + int smallPatchedFieldIdsOffset + = smallPatchedProtoIdsOffset + smallPatchedProtoIdsSize; + if (oldDex.getTableOfContents().fieldIds.isElementFourByteAligned) { + smallPatchedFieldIdsOffset = SizeOf.roundToTimesOfFour(smallPatchedFieldIdsOffset); + } + patchedDexToSmallPatchedFieldIdOffsetMap.put( + patchedDex, smallPatchedFieldIdsOffset + ); + + int smallPatchedMethodIdsOffset + = smallPatchedFieldIdsOffset + smallPatchedFieldIdsSize; + if (oldDex.getTableOfContents().methodIds.isElementFourByteAligned) { + smallPatchedMethodIdsOffset + = SizeOf.roundToTimesOfFour(smallPatchedMethodIdsOffset); + } + patchedDexToSmallPatchedMethodIdOffsetMap.put( + patchedDex, smallPatchedMethodIdsOffset + ); + + int smallPatchedAnnotationsOffset + = smallPatchedTypeListsOffset + smallPatchedTypeListsSize; + if (oldDex.getTableOfContents().annotations.isElementFourByteAligned) { + smallPatchedAnnotationsOffset + = SizeOf.roundToTimesOfFour(smallPatchedAnnotationsOffset); + } + patchedDexToSmallPatchedAnnotationOffsetMap.put( + patchedDex, smallPatchedAnnotationsOffset + ); + int smallPatchedAnnotationsSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().annotations, + fullToSmallPatchIndexMap, + patchedDexToCollectedAnnotationIndicesMap.get(patchedDex) + ).simulate(smallPatchedAnnotationsOffset); + if (smallPatchedAnnotationsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedAnnotationSetsOffset + = smallPatchedAnnotationsOffset + smallPatchedAnnotationsSize; + if (oldDex.getTableOfContents().annotationSets.isElementFourByteAligned) { + smallPatchedAnnotationSetsOffset + = SizeOf.roundToTimesOfFour(smallPatchedAnnotationSetsOffset); + } + patchedDexToSmallPatchedAnnotationSetOffsetMap.put( + patchedDex, smallPatchedAnnotationSetsOffset + ); + int smallPatchedAnnotationSetsSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().annotationSets, + fullToSmallPatchIndexMap, + patchedDexToCollectedAnnotationSetIndicesMap.get(patchedDex) + ).simulate(smallPatchedAnnotationSetsOffset); + if (smallPatchedAnnotationSetsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedAnnotationSetRefListsOffset + = smallPatchedAnnotationSetsOffset + + smallPatchedAnnotationSetsSize; + if (oldDex.getTableOfContents().annotationSetRefLists.isElementFourByteAligned) { + smallPatchedAnnotationSetRefListsOffset + = SizeOf.roundToTimesOfFour(smallPatchedAnnotationSetRefListsOffset); + } + patchedDexToSmallPatchedAnnotationSetRefListOffsetMap.put( + patchedDex, smallPatchedAnnotationSetRefListsOffset + ); + int smallPatchedAnnotationSetRefListsSize + = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().annotationSetRefLists, + fullToSmallPatchIndexMap, + patchedDexToCollectedAnnotationSetRefListIndicesMap.get(patchedDex) + ).simulate(smallPatchedAnnotationSetRefListsOffset); + if (smallPatchedAnnotationSetRefListsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedAnnotationsDirectoriesOffset + = smallPatchedAnnotationSetRefListsOffset + + smallPatchedAnnotationSetRefListsSize; + if (oldDex.getTableOfContents().annotationsDirectories.isElementFourByteAligned) { + smallPatchedAnnotationsDirectoriesOffset + = SizeOf.roundToTimesOfFour(smallPatchedAnnotationsDirectoriesOffset); + } + patchedDexToSmallPatchedAnnotationsDirectoryOffsetMap.put( + patchedDex, smallPatchedAnnotationsDirectoriesOffset + ); + int smallPatchedAnnotationsDirectoriesSize + = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().annotationsDirectories, + fullToSmallPatchIndexMap, + patchedDexToCollectedAnnotationsDirectoryIndicesMap.get(patchedDex) + ).simulate(smallPatchedAnnotationsDirectoriesOffset); + if (smallPatchedAnnotationsDirectoriesSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedDebugInfoItemsOffset + = smallPatchedAnnotationsDirectoriesOffset + + smallPatchedAnnotationsDirectoriesSize; + if (oldDex.getTableOfContents().debugInfos.isElementFourByteAligned) { + smallPatchedDebugInfoItemsOffset + = SizeOf.roundToTimesOfFour(smallPatchedDebugInfoItemsOffset); + } + patchedDexToSmallPatchedDebugInfoOffsetMap.put( + patchedDex, smallPatchedDebugInfoItemsOffset + ); + int smallPatchedDebugInfoItemsSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().debugInfos, + fullToSmallPatchIndexMap, + patchedDexToCollectedDebugInfoIndicesMap.get(patchedDex) + ).simulate(smallPatchedDebugInfoItemsOffset); + if (smallPatchedDebugInfoItemsSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedCodesOffset + = smallPatchedDebugInfoItemsOffset + + smallPatchedDebugInfoItemsSize; + if (oldDex.getTableOfContents().codes.isElementFourByteAligned) { + smallPatchedCodesOffset + = SizeOf.roundToTimesOfFour(smallPatchedCodesOffset); + } + patchedDexToSmallPatchedCodeOffsetMap.put( + patchedDex, smallPatchedCodesOffset + ); + int smallPatchedCodesSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().codes, + fullToSmallPatchIndexMap, + patchedDexToCollectedCodeIndicesMap.get(patchedDex) + ).simulate(smallPatchedCodesOffset); + if (smallPatchedCodesSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedClassDatasOffset + = smallPatchedCodesOffset + + smallPatchedCodesSize; + if (oldDex.getTableOfContents().classDatas.isElementFourByteAligned) { + smallPatchedClassDatasOffset + = SizeOf.roundToTimesOfFour(smallPatchedClassDatasOffset); + } + patchedDexToSmallPatchedClassDataOffsetMap.put( + patchedDex, smallPatchedClassDatasOffset + ); + int smallPatchedClassDatasSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().classDatas, + fullToSmallPatchIndexMap, + patchedDexToCollectedClassDataIndicesMap.get(patchedDex) + ).simulate(smallPatchedClassDatasOffset); + if (smallPatchedClassDatasSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedEncodedArraysOffset + = smallPatchedClassDatasOffset + + smallPatchedClassDatasSize; + if (oldDex.getTableOfContents().encodedArrays.isElementFourByteAligned) { + smallPatchedEncodedArraysOffset + = SizeOf.roundToTimesOfFour(smallPatchedEncodedArraysOffset); + } + patchedDexToSmallPatchedEncodedArrayOffsetMap.put( + patchedDex, smallPatchedEncodedArraysOffset + ); + int smallPatchedEncodedArraysSize = new SmallPatchSimulator( + patchedDex, + patchedDex.getTableOfContents().encodedArrays, + fullToSmallPatchIndexMap, + patchedDexToCollectedEncodedArrayIndicesMap.get(patchedDex) + ).simulate(smallPatchedEncodedArraysOffset); + if (smallPatchedEncodedArraysSize > 0) { + ++smallPatchedSectionCount; + } + + int smallPatchedClassDefsOffset + = smallPatchedMethodIdsOffset + + smallPatchedMethodIdsSize; + if (oldDex.getTableOfContents().classDefs.isElementFourByteAligned) { + smallPatchedClassDefsOffset + = SizeOf.roundToTimesOfFour(smallPatchedClassDefsOffset); + } + patchedDexToSmallPatchedClassDefOffsetMap.put( + patchedDex, smallPatchedClassDefsOffset + ); + + // Calculate any values we still know nothing about them. + int smallPatchedMapListOffset + = smallPatchedEncodedArraysOffset + + smallPatchedEncodedArraysSize; + if (oldDex.getTableOfContents().mapList.isElementFourByteAligned) { + smallPatchedMapListOffset + = SizeOf.roundToTimesOfFour(smallPatchedMapListOffset); + } + patchedDexToSmallPatchedMapListOffsetMap.put( + patchedDex, smallPatchedMapListOffset + ); + int smallPatchedMapListSize + = SizeOf.UINT + SizeOf.MAP_ITEM * smallPatchedSectionCount; + + int smallPatchedDexSize + = smallPatchedMapListOffset + + smallPatchedMapListSize; + patchedDexToSmallPatchedDexSizeMap.put(patchedDex, smallPatchedDexSize); + } + } + + private int getCollectedIndicesCountSafely( + Map> collectedIndicesMap, Dex patchedDex + ) { + Set indices = collectedIndicesMap.get(patchedDex); + if (indices == null) { + return 0; + } else { + return indices.size(); + } + } + + private void saveToStream(OutputStream os) throws IOException { + DexDataBuffer buffer = new DexDataBuffer(); + + // Write header + buffer.write(SmallPatchedDexItemFile.MAGIC); + buffer.writeShort(SmallPatchedDexItemFile.CURRENT_VERSION); + // Take the field 'firstChunkOffset' into header's size account. + buffer.writeInt(buffer.position() + SizeOf.UINT); + + // Gather old dexes + List oldDexes = new ArrayList<>(); + int oldDexGroupCount = this.oldDexGroups.size(); + for (int i = 0; i < oldDexGroupCount; ++i) { + DexGroup oldDexGroup = oldDexGroups.get(i); + for (Dex oldDex : oldDexGroup.dexes) { + oldDexes.add(oldDex); + } + } + + // Gather patched dexes + List patchedDexes = new ArrayList<>(); + int patchedDexGroupCount = this.patchedDexGroups.size(); + for (int i = 0; i < patchedDexGroupCount; ++i) { + DexGroup patchedDexGroup = patchedDexGroups.get(i); + for (Dex patchedDex : patchedDexGroup.dexes) { + patchedDexes.add(patchedDex); + } + } + + // Dex sign chunk + int oldDexSignCount = oldDexes.size(); + buffer.writeUleb128(oldDexSignCount); + + Map oldDexSignToIdxInSignList = new HashMap<>(); + for (int i = 0; i < oldDexSignCount; ++i) { + final byte[] signBytes = oldDexes.get(i).computeSignature(false); + final String signStr = Hex.toHexString(signBytes); + buffer.write(signBytes); + oldDexSignToIdxInSignList.put(signStr, i); + } + + for (Dex patchedDex : patchedDexes) { + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedStringIdOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedTypeIdOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedProtoIdOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedFieldIdOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedMethodIdOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedClassDefOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedStringDataOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedTypeListOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedAnnotationOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedAnnotationSetOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedAnnotationSetRefListOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedAnnotationsDirectoryOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedDebugInfoOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedCodeOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedClassDataOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedEncodedArrayOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedMapListOffsetMap + ); + writeSmallPatchedSectionOffset( + buffer, patchedDex, patchedDexToSmallPatchedDexSizeMap + ); + } + + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedStringIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedTypeIdIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedTypeListIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedProtoIdIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedFieldIdIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedMethodIdIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedAnnotationIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedAnnotationSetIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedAnnotationSetRefListIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedAnnotationsDirectoryIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedEncodedArrayIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedDebugInfoIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedCodeIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedClassDataIndicesMap); + writeDataChunk(buffer, patchedDexes, patchedDexToCollectedClassDefIndicesMap); + + os.write(buffer.array()); + os.flush(); + } + + private void writeSmallPatchedSectionOffset( + DexDataBuffer buffer, + Dex patchedDex, + Map patchedDexToSmallPatchedSectionOffsetMap + ) { + Integer offset = patchedDexToSmallPatchedSectionOffsetMap.get(patchedDex); + if (offset != null) { + buffer.writeInt(offset); + } else { + throw new IllegalStateException("section offset is missing."); + } + } + + private void writeDataChunk( + DexDataBuffer buffer, + List patchedDexList, + Map> patchedDexToCollectedItemIndicesMap + ) { + for (Dex patchedDex : patchedDexList) { + Set itemIndices = patchedDexToCollectedItemIndicesMap.get(patchedDex); + if (itemIndices == null) { + buffer.writeUleb128(0); + } else { + int indexCount = itemIndices.size(); + Integer[] itemIndexArr = new Integer[indexCount]; + itemIndices.toArray(itemIndexArr); + Arrays.sort(itemIndexArr); + buffer.writeUleb128(indexCount); + int prevIndex = 0; + for (int j = 0; j < indexCount; ++j) { + buffer.writeSleb128(itemIndexArr[j] - prevIndex); + prevIndex = itemIndexArr[j]; + } + } + } + } + + private boolean isClassMethodReferenceToRefAffectedClass( + Dex owner, + ClassData.Method[] methods, + Collection affectedClassDescs + ) { + if (affectedClassDescs.isEmpty() || methods == null || methods.length == 0) { + return false; + } + + for (ClassData.Method method : methods) { + if (method.codeOffset == 0) { + continue; + } + Code code = owner.readCode(method); + RefToRefAffectedClassInsnVisitor refInsnVisitor = + new RefToRefAffectedClassInsnVisitor(owner, method, affectedClassDescs); + InstructionReader insnReader = + new InstructionReader(new ShortArrayCodeInput(code.instructions)); + try { + insnReader.accept(refInsnVisitor); + if (refInsnVisitor.isMethodReferencedToRefAffectedClass) { + return true; + } + } catch (EOFException e) { + throw new IllegalStateException(e); + } + } + + return false; + } + + private void collectItemIndicesFromDexGroup( + DexGroup oldDexGroup, + DexGroup patchedDexGroup + ) { + DexClassesComparator dexClassesCmp = new DexClassesComparator("*"); + dexClassesCmp.setCompareMode(DexClassesComparator.COMPARE_MODE_CAUSE_REF_CHANGE_ONLY); + dexClassesCmp.setIgnoredRemovedClassDescPattern(this.loaderClassPatterns); + dexClassesCmp.startCheck(oldDexGroup, patchedDexGroup); + + Set refAffectedClassDescs + = dexClassesCmp.getChangedClassDescToInfosMap().keySet(); + + Set classInfosInPatchedDexGroup + = patchedDexGroup.getClassInfosInDexesWithDuplicateCheck(); + + Set patchedClassInfosForItemIndexCollecting = new HashSet<>(); + + for (DexClassInfo patchedClassInfo : classInfosInPatchedDexGroup) { + if (patchedClassInfo.classDef.classDataOffset == 0) { + continue; + } + ClassData patchedClassData + = patchedClassInfo.owner.readClassData(patchedClassInfo.classDef); + + boolean shouldAdd = isClassMethodReferenceToRefAffectedClass( + patchedClassInfo.owner, + patchedClassData.directMethods, + refAffectedClassDescs + ); + + if (!shouldAdd) { + shouldAdd = isClassMethodReferenceToRefAffectedClass( + patchedClassInfo.owner, + patchedClassData.virtualMethods, + refAffectedClassDescs + ); + } + + if (shouldAdd) { + logger.i(TAG, "Add class %s to small patched dex.", patchedClassInfo.classDesc); + patchedClassInfosForItemIndexCollecting.add(patchedClassInfo); + } + } + + // So far we get descriptors of classes we need to add additionally, + // while we still need to do a fully compare to collect added classes + // and replaced classes since they may use items in their owner dex which + // is not modified. + dexClassesCmp.setCompareMode(DexClassesComparator.COMPARE_MODE_NORMAL); + dexClassesCmp.startCheck(oldDexGroup, patchedDexGroup); + + Collection addedClassInfos = dexClassesCmp.getAddedClassInfos(); + for (DexClassInfo addClassInfo : addedClassInfos) { + logger.i(TAG, "Add class %s to small patched dex.", addClassInfo.classDesc); + patchedClassInfosForItemIndexCollecting.add(addClassInfo); + } + + Collection changedOldPatchedClassInfos = + dexClassesCmp.getChangedClassDescToInfosMap().values(); + + // changedOldPatchedClassInfo[1] means changedPatchedClassInfo + for (DexClassInfo[] changedOldPatchedClassInfo : changedOldPatchedClassInfos) { + logger.i(TAG, "Add class %s to small patched dex.", changedOldPatchedClassInfo[1].classDesc); + patchedClassInfosForItemIndexCollecting.add(changedOldPatchedClassInfo[1]); + } + + // Finally we collect all elements' indices of collected class. + + Map dexToOffsetToIndexConverterMap = new HashMap<>(); + + for (DexClassInfo classInfo : patchedClassInfosForItemIndexCollecting) { + Dex owner = classInfo.owner; + OffsetToIndexConverter offsetToIndexConverter = + dexToOffsetToIndexConverterMap.get(owner); + + if (offsetToIndexConverter == null) { + offsetToIndexConverter = new OffsetToIndexConverter(owner); + dexToOffsetToIndexConverterMap.put(owner, offsetToIndexConverter); + } + + collectItemIndicesFromClassInfo(classInfo, offsetToIndexConverter); + } + } + + private void collectItemIndicesFromClassInfo( + DexClassInfo classInfo, + OffsetToIndexConverter offsetToIndexConverter + ) { + Dex owner = classInfo.owner; + + putValueIntoSetMap( + patchedDexToCollectedClassDefIndicesMap, + owner, + classInfo.classDefIndex + ); + + collectItemIndicesFromTypeIndex( + owner, classInfo.classDef.typeIndex, offsetToIndexConverter + ); + + collectItemIndicesFromTypeIndex( + owner, classInfo.classDef.supertypeIndex, offsetToIndexConverter + ); + + collectItemIndicesFromTypeList( + owner, classInfo.classDef.interfacesOffset, offsetToIndexConverter + ); + + collectItemIndicesFromStringIndex( + owner, classInfo.classDef.sourceFileIndex, offsetToIndexConverter + ); + + collectItemIndicesFromAnnotationsDirectory( + owner, classInfo.classDef.annotationsOffset, offsetToIndexConverter + ); + + collectItemIndicesFromClassData( + owner, classInfo.classDef.classDataOffset, offsetToIndexConverter + ); + + collectItemIndicesFromEncodedArray( + owner, classInfo.classDef.staticValuesOffset, offsetToIndexConverter + ); + } + + private void collectItemIndicesFromStringIndex( + Dex owner, + int stringIndex, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (stringIndex == ClassDef.NO_INDEX) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedStringIndicesMap, + owner, + stringIndex + ); + } + + private void collectItemIndicesFromTypeList( + Dex owner, + int typeListOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (typeListOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedTypeListIndicesMap, + owner, + offsetToIndexConverter.getTypeListIndexByOffset(typeListOffset) + ); + + TypeList typeList = owner.openSection(typeListOffset).readTypeList(); + for (int typeIndex : typeList.types) { + collectItemIndicesFromTypeIndex( + owner, typeIndex, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromTypeIndex( + Dex owner, + int typeIndex, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (typeIndex == ClassDef.NO_INDEX) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedTypeIdIndicesMap, + owner, + typeIndex + ); + + collectItemIndicesFromStringIndex( + owner, owner.typeIds().get(typeIndex), offsetToIndexConverter + ); + } + + private void collectItemIndicesFromFieldIndex( + Dex owner, + int fieldIndex, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (fieldIndex == ClassDef.NO_INDEX) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedFieldIdIndicesMap, + owner, + fieldIndex + ); + + FieldId fieldId = owner.fieldIds().get(fieldIndex); + collectItemIndicesFromStringIndex(owner, fieldId.nameIndex, offsetToIndexConverter); + collectItemIndicesFromTypeIndex( + owner, fieldId.declaringClassIndex, offsetToIndexConverter + ); + collectItemIndicesFromTypeIndex(owner, fieldId.typeIndex, offsetToIndexConverter); + } + + private void collectItemIndicesFromMethodIndex( + Dex owner, + int methodIndex, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (methodIndex == ClassDef.NO_INDEX) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedMethodIdIndicesMap, + owner, + methodIndex + ); + + MethodId methodId = owner.methodIds().get(methodIndex); + collectItemIndicesFromStringIndex( + owner, methodId.nameIndex, offsetToIndexConverter + ); + collectItemIndicesFromTypeIndex( + owner, methodId.declaringClassIndex, offsetToIndexConverter + ); + collectItemIndicesFromProtoIndex( + owner, methodId.protoIndex, offsetToIndexConverter + ); + } + + private void collectItemIndicesFromProtoIndex( + Dex owner, + int protoIndex, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (protoIndex == ClassDef.NO_INDEX) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedProtoIdIndicesMap, + owner, + protoIndex + ); + + ProtoId protoId = owner.protoIds().get(protoIndex); + + collectItemIndicesFromStringIndex( + owner, protoId.shortyIndex, offsetToIndexConverter + ); + collectItemIndicesFromTypeIndex( + owner, protoId.returnTypeIndex, offsetToIndexConverter + ); + collectItemIndicesFromTypeList( + owner, protoId.parametersOffset, offsetToIndexConverter + ); + } + + private void collectItemIndicesFromAnnotationsDirectory( + Dex owner, + int annotationsDirectoryOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (annotationsDirectoryOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedAnnotationsDirectoryIndicesMap, + owner, + offsetToIndexConverter.getAnnotationsDirectoryIndexByOffset( + annotationsDirectoryOffset + ) + ); + + AnnotationsDirectory annotationsDirectory = + owner.openSection(annotationsDirectoryOffset).readAnnotationsDirectory(); + + collectItemIndicesFromAnnotationSet( + owner, + annotationsDirectory.classAnnotationsOffset, + offsetToIndexConverter + ); + + for (int[] fieldAnnoPair : annotationsDirectory.fieldAnnotations) { + collectItemIndicesFromFieldIndex( + owner, fieldAnnoPair[0], offsetToIndexConverter + ); + collectItemIndicesFromAnnotationSet( + owner, fieldAnnoPair[1], offsetToIndexConverter + ); + } + for (int[] methodAnnoPair : annotationsDirectory.methodAnnotations) { + collectItemIndicesFromMethodIndex( + owner, methodAnnoPair[0], offsetToIndexConverter + ); + collectItemIndicesFromAnnotationSet( + owner, methodAnnoPair[1], offsetToIndexConverter + ); + } + for (int[] paramAnnoPair : annotationsDirectory.parameterAnnotations) { + collectItemIndicesFromMethodIndex( + owner, paramAnnoPair[0], offsetToIndexConverter + ); + collectItemIndicesFromAnnotationSetRefList( + owner, paramAnnoPair[1], offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromAnnotationSetRefList( + Dex owner, + int annotationSetRefListOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (annotationSetRefListOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedAnnotationSetRefListIndicesMap, + owner, + offsetToIndexConverter.getAnnotationSetRefListIndexByOffset( + annotationSetRefListOffset + ) + ); + + AnnotationSetRefList annotationSetRefList = + owner.openSection(annotationSetRefListOffset).readAnnotationSetRefList(); + + for (int annotationSetOffset : annotationSetRefList.annotationSetRefItems) { + collectItemIndicesFromAnnotationSet( + owner, annotationSetOffset, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromAnnotationSet( + Dex owner, + int annotationSetOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (annotationSetOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedAnnotationSetIndicesMap, + owner, + offsetToIndexConverter.getAnnotationSetIndexByOffset( + annotationSetOffset + ) + ); + + AnnotationSet annotationSet = owner.openSection(annotationSetOffset).readAnnotationSet(); + + for (int annotationOffset : annotationSet.annotationOffsets) { + collectItemIndicesFromAnnotation( + owner, annotationOffset, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromAnnotation( + Dex owner, + int annotationOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (annotationOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedAnnotationIndicesMap, + owner, + offsetToIndexConverter.getAnnotationIndexByOffset( + annotationOffset + ) + ); + + Annotation annotation = + owner.openSection(annotationOffset).readAnnotation(); + + EncodedValueReader annotationReader = annotation.getReader(); + + collectItemIndicesFromAnnotationReader( + owner, + annotationReader, + offsetToIndexConverter + ); + } + + private void collectItemIndicesFromAnnotationReader( + Dex owner, + EncodedValueReader annotationReader, + OffsetToIndexConverter offsetToIndexConverter + ) { + int fieldCount = annotationReader.readAnnotation(); + + collectItemIndicesFromTypeIndex( + owner, annotationReader.getAnnotationType(), offsetToIndexConverter + ); + + for (int i = 0; i < fieldCount; ++i) { + int annotationNameIndex = annotationReader.readAnnotationName(); + collectItemIndicesFromStringIndex( + owner, annotationNameIndex, offsetToIndexConverter + ); + collectItemIndicesFromEncodedValueReader( + owner, annotationReader, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromEncodedArrayReader( + Dex owner, + EncodedValueReader arrayReader, + OffsetToIndexConverter offsetToIndexConverter + ) { + int size = arrayReader.readArray(); + for (int i = 0; i < size; ++i) { + collectItemIndicesFromEncodedValueReader( + owner, arrayReader, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromEncodedValueReader( + Dex owner, + EncodedValueReader encodedValueReader, + OffsetToIndexConverter offsetToIndexConverter + ) { + switch (encodedValueReader.peek()) { + case EncodedValueReader.ENCODED_BYTE: + // Skip value. + encodedValueReader.readByte(); + break; + case EncodedValueReader.ENCODED_SHORT: + // Skip value. + encodedValueReader.readShort(); + break; + case EncodedValueReader.ENCODED_INT: + // Skip value. + encodedValueReader.readInt(); + break; + case EncodedValueReader.ENCODED_LONG: + // Skip value. + encodedValueReader.readLong(); + break; + case EncodedValueReader.ENCODED_CHAR: + // Skip value. + encodedValueReader.readChar(); + break; + case EncodedValueReader.ENCODED_FLOAT: + // Skip value. + encodedValueReader.readFloat(); + break; + case EncodedValueReader.ENCODED_DOUBLE: + // Skip value. + encodedValueReader.readDouble(); + break; + case EncodedValueReader.ENCODED_STRING: + collectItemIndicesFromStringIndex( + owner, + encodedValueReader.readString(), + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_TYPE: + collectItemIndicesFromTypeIndex( + owner, + encodedValueReader.readType(), + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_FIELD: + collectItemIndicesFromFieldIndex( + owner, + encodedValueReader.readField(), + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_ENUM: + collectItemIndicesFromFieldIndex( + owner, + encodedValueReader.readEnum(), + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_METHOD: + collectItemIndicesFromMethodIndex( + owner, + encodedValueReader.readMethod(), + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_ARRAY: + collectItemIndicesFromEncodedArrayReader( + owner, + encodedValueReader, + offsetToIndexConverter + ); + break; + case EncodedValueReader.ENCODED_ANNOTATION: + collectItemIndicesFromAnnotationReader( + owner, + encodedValueReader, + offsetToIndexConverter + + ); + break; + case EncodedValueReader.ENCODED_NULL: + // Skip value. + encodedValueReader.readNull(); + break; + case EncodedValueReader.ENCODED_BOOLEAN: + // Skip value. + encodedValueReader.readBoolean(); + break; + default: + throw new DexException( + "Unexpected type: " + Integer.toHexString(encodedValueReader.peek()) + ); + } + } + + private void collectItemIndicesFromClassData( + Dex owner, + int classDataOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (classDataOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedClassDataIndicesMap, + owner, + offsetToIndexConverter.getClassDataIndexByOffset(classDataOffset) + ); + + ClassData classData = owner.openSection(classDataOffset).readClassData(); + + for (ClassData.Field field : classData.instanceFields) { + collectItemIndicesFromFieldIndex( + owner, field.fieldIndex, offsetToIndexConverter + ); + } + + for (ClassData.Field field : classData.staticFields) { + collectItemIndicesFromFieldIndex( + owner, field.fieldIndex, offsetToIndexConverter + ); + } + + for (ClassData.Method method : classData.directMethods) { + collectItemIndicesFromMethodIndex( + owner, method.methodIndex, offsetToIndexConverter + ); + collectItemIndicesFromCode( + owner, method.codeOffset, offsetToIndexConverter + ); + } + + for (ClassData.Method method : classData.virtualMethods) { + collectItemIndicesFromMethodIndex( + owner, method.methodIndex, offsetToIndexConverter + ); + collectItemIndicesFromCode( + owner, method.codeOffset, offsetToIndexConverter + ); + } + } + + private void collectItemIndicesFromCode( + Dex owner, + int codeOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (codeOffset == ClassDef.NO_OFFSET) { + return; + } + + + putValueIntoSetMap( + patchedDexToCollectedCodeIndicesMap, + owner, + offsetToIndexConverter.getCodeIndexByOffset(codeOffset) + ); + + Code code = owner.openSection(codeOffset).readCode(); + + collectItemIndicesFromDebugInfoItem( + owner, + code.debugInfoOffset, + offsetToIndexConverter + ); + + InstructionReader ir = new InstructionReader(new ShortArrayCodeInput(code.instructions)); + try { + ir.accept(new IndicesCollectorInsnVisitor( + owner, offsetToIndexConverter + )); + } catch (EOFException e) { + throw new IllegalStateException(e); + } + + for (Code.CatchHandler catchHandler : code.catchHandlers) { + for (int typeIndex : catchHandler.typeIndexes) { + collectItemIndicesFromTypeIndex( + owner, + typeIndex, + offsetToIndexConverter + ); + } + } + } + + private void collectItemIndicesFromDebugInfoItem( + Dex owner, + int debugInfoItemOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (debugInfoItemOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedDebugInfoIndicesMap, + owner, + offsetToIndexConverter.getDebugInfoItemIndexByOffset(debugInfoItemOffset) + ); + + DebugInfoItem debugInfoItem = owner.openSection(debugInfoItemOffset).readDebugInfoItem(); + + for (int stringIndex : debugInfoItem.parameterNames) { + collectItemIndicesFromStringIndex( + owner, stringIndex, offsetToIndexConverter + ); + } + + final ByteArrayInputStream bais = new ByteArrayInputStream(debugInfoItem.infoSTM); + ByteInput inAdapter = new ByteInput() { + @Override + public byte readByte() { + return (byte) (bais.read() & 0xFF); + } + }; + + outside_whileloop: + while (true) { + int opcode = bais.read() & 0xFF; + switch (opcode) { + case DebugInfoItem.DBG_END_SEQUENCE: { + break outside_whileloop; + } + case DebugInfoItem.DBG_ADVANCE_PC: { + // Skip addrDiff. + int addrDiff = Leb128.readUnsignedLeb128(inAdapter); + break; + } + case DebugInfoItem.DBG_ADVANCE_LINE: { + // Skip lineDiff. + int lineDiff = Leb128.readSignedLeb128(inAdapter); + break; + } + case DebugInfoItem.DBG_START_LOCAL: + case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { + // Skip registerNum. + int registerNum = Leb128.readUnsignedLeb128(inAdapter); + + int nameIndex = Leb128.readUnsignedLeb128p1(inAdapter); + collectItemIndicesFromStringIndex( + owner, nameIndex, offsetToIndexConverter + ); + + int typeIndex = Leb128.readUnsignedLeb128p1(inAdapter); + collectItemIndicesFromTypeIndex( + owner, typeIndex, offsetToIndexConverter + ); + + if (opcode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { + int sigIndex = Leb128.readUnsignedLeb128p1(inAdapter); + collectItemIndicesFromStringIndex( + owner, sigIndex, offsetToIndexConverter + ); + } + break; + } + case DebugInfoItem.DBG_END_LOCAL: + case DebugInfoItem.DBG_RESTART_LOCAL: { + // Skip registerNum. + int registerNum = Leb128.readUnsignedLeb128(inAdapter); + break; + } + case DebugInfoItem.DBG_SET_FILE: { + int nameIndex = Leb128.readUnsignedLeb128p1(inAdapter); + collectItemIndicesFromStringIndex( + owner, nameIndex, offsetToIndexConverter + ); + break; + } + case DebugInfoItem.DBG_SET_PROLOGUE_END: + case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: + default: { + break; + } + } + } + } + + private void collectItemIndicesFromEncodedArray( + Dex owner, + int encodedArrayOffset, + OffsetToIndexConverter offsetToIndexConverter + ) { + if (encodedArrayOffset == ClassDef.NO_OFFSET) { + return; + } + + putValueIntoSetMap( + patchedDexToCollectedEncodedArrayIndicesMap, + owner, + offsetToIndexConverter.getEncodedArrayIndexByOffset(encodedArrayOffset) + ); + + EncodedValue arrayVal = owner.openSection(encodedArrayOffset).readEncodedArray(); + EncodedValueReader arrayReader = + new EncodedValueReader(arrayVal, EncodedValueReader.ENCODED_ARRAY); + + collectItemIndicesFromEncodedArrayReader( + owner, arrayReader, offsetToIndexConverter + ); + } + + private void putValueIntoSetMap(Map> map, K key, V value) { + Set valueSet = map.get(key); + if (valueSet == null) { + valueSet = new HashSet<>(); + map.put(key, valueSet); + } + valueSet.add(value); + } + + private class SmallPatchSimulator> { + private final TableOfContents.Section tocSec; + private final Dex.Section patchedSection; + private final int patchedItemCount; + private final IndexMap fullToSmallPatchMap; + private final Set collectedIndices; + + SmallPatchSimulator( + Dex patchedDex, + TableOfContents.Section tocSec, + IndexMap fullToSmallPatchMap, + Set collectedIndices + ) { + if (tocSec.exists()) { + this.tocSec = tocSec; + this.patchedSection = patchedDex.openSection(tocSec); + this.patchedItemCount = tocSec.size; + this.fullToSmallPatchMap = fullToSmallPatchMap; + this.collectedIndices = collectedIndices; + } else { + this.tocSec = null; + this.patchedSection = null; + this.patchedItemCount = 0; + this.fullToSmallPatchMap = null; + this.collectedIndices = null; + } + } + + private int getItemIndexOrOffset(T item, int index) { + if (item instanceof TableOfContents.Section.Item) { + return ((TableOfContents.Section.Item) item).off; + } else { + return index; + } + } + + @SuppressWarnings("unchecked") + private T nextItem(DexDataBuffer buffer) { + switch (this.tocSec.type) { + case TableOfContents.SECTION_TYPE_TYPEIDS: { + return (T) (Integer) buffer.readInt(); + } + case TableOfContents.SECTION_TYPE_PROTOIDS: { + return (T) buffer.readProtoId(); + } + case TableOfContents.SECTION_TYPE_FIELDIDS: { + return (T) buffer.readFieldId(); + } + case TableOfContents.SECTION_TYPE_METHODIDS: { + return (T) buffer.readMethodId(); + } + case TableOfContents.SECTION_TYPE_CLASSDEFS: { + return (T) buffer.readClassDef(); + } + case TableOfContents.SECTION_TYPE_STRINGDATAS: { + return (T) buffer.readStringData(); + } + case TableOfContents.SECTION_TYPE_TYPELISTS: { + return (T) buffer.readTypeList(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONS: { + return (T) buffer.readAnnotation(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETS: { + return (T) buffer.readAnnotationSet(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETREFLISTS: { + return (T) buffer.readAnnotationSetRefList(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + return (T) buffer.readAnnotationsDirectory(); + } + case TableOfContents.SECTION_TYPE_DEBUGINFOS: { + return (T) buffer.readDebugInfoItem(); + } + case TableOfContents.SECTION_TYPE_CODES: { + return (T) buffer.readCode(); + } + case TableOfContents.SECTION_TYPE_ENCODEDARRAYS: { + return (T) buffer.readEncodedArray(); + } + case TableOfContents.SECTION_TYPE_CLASSDATA: { + return (T) buffer.readClassData(); + } + default: + throw new IllegalStateException("unknown section type: " + this.tocSec.type); + } + } + + private int getItemSize(T item) { + if (item instanceof TableOfContents.Section.Item) { + return ((TableOfContents.Section.Item) item).byteCountInDex(); + } else { + if (item instanceof Integer) { + return SizeOf.UINT; + } else { + throw new IllegalStateException( + "unexpected item type: " + item.getClass().getName() + ); + } + } + } + + @SuppressWarnings("unchecked") + private T adjustItem(IndexMap indexMap, T item) { + switch (this.tocSec.type) { + case TableOfContents.SECTION_TYPE_TYPEIDS: { + return (T) (Integer) indexMap.adjustStringIndex((Integer) item); + } + case TableOfContents.SECTION_TYPE_PROTOIDS: { + return (T) indexMap.adjust((ProtoId) item); + } + case TableOfContents.SECTION_TYPE_FIELDIDS: { + return (T) indexMap.adjust((FieldId) item); + } + case TableOfContents.SECTION_TYPE_METHODIDS: { + return (T) indexMap.adjust((MethodId) item); + } + case TableOfContents.SECTION_TYPE_CLASSDEFS: { + return (T) indexMap.adjust((ClassDef) item); + } + case TableOfContents.SECTION_TYPE_STRINGDATAS: { + // nothing to do. + return item; + } + case TableOfContents.SECTION_TYPE_TYPELISTS: { + return (T) indexMap.adjust((TypeList) item); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONS: { + return (T) indexMap.adjust((Annotation) item); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETS: { + return (T) indexMap.adjust((AnnotationSet) item); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETREFLISTS: { + return (T) indexMap.adjust((AnnotationSetRefList) item); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + return (T) indexMap.adjust((AnnotationsDirectory) item); + } + case TableOfContents.SECTION_TYPE_DEBUGINFOS: { + return (T) indexMap.adjust((DebugInfoItem) item); + } + case TableOfContents.SECTION_TYPE_CODES: { + return (T) indexMap.adjust((Code) item); + } + case TableOfContents.SECTION_TYPE_ENCODEDARRAYS: { + return (T) indexMap.adjust((EncodedValue) item); + } + case TableOfContents.SECTION_TYPE_CLASSDATA: { + return (T) indexMap.adjust((ClassData) item); + } + default: + throw new IllegalStateException("unknown section type: " + this.tocSec.type); + } + } + + @SuppressWarnings("unchecked") + private void updateIndexOrOffset( + IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset + ) { + switch (this.tocSec.type) { + case TableOfContents.SECTION_TYPE_TYPEIDS: { + indexMap.mapTypeIds(oldIndex, newIndex); + break; + } + case TableOfContents.SECTION_TYPE_PROTOIDS: { + indexMap.mapProtoIds(oldIndex, newIndex); + break; + } + case TableOfContents.SECTION_TYPE_FIELDIDS: { + indexMap.mapFieldIds(oldIndex, newIndex); + break; + } + case TableOfContents.SECTION_TYPE_METHODIDS: { + indexMap.mapMethodIds(oldIndex, newIndex); + break; + } + case TableOfContents.SECTION_TYPE_CLASSDEFS: { + // nothing to do. + break; + } + case TableOfContents.SECTION_TYPE_STRINGDATAS: { + indexMap.mapStringIds(oldIndex, newIndex); + break; + } + case TableOfContents.SECTION_TYPE_TYPELISTS: { + indexMap.mapTypeListOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_ANNOTATIONS: { + indexMap.mapAnnotationOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETS: { + indexMap.mapAnnotationSetOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETREFLISTS: { + indexMap.mapAnnotationSetRefListOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + indexMap.mapAnnotationsDirectoryOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_DEBUGINFOS: { + indexMap.mapDebugInfoItemOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_CODES: { + indexMap.mapCodeOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_ENCODEDARRAYS: { + indexMap.mapStaticValuesOffset(oldOffset, newOffset); + break; + } + case TableOfContents.SECTION_TYPE_CLASSDATA: { + indexMap.mapClassDataOffset(oldOffset, newOffset); + break; + } + default: + throw new IllegalStateException("unknown section type: " + this.tocSec.type); + } + } + + public int simulate(int smallPatchBaseOffset) { + if (patchedSection == null) { + return 0; + } + if (collectedIndices == null || collectedIndices.isEmpty()) { + return 0; + } + int smallPatchedIndex = 0; + int smallPatchOffset = smallPatchBaseOffset; + for (int fullPatchedItemIndex = 0; + fullPatchedItemIndex < this.patchedItemCount; + ++fullPatchedItemIndex + ) { + T fullPatchedItemInSmallPatch = adjustItem( + this.fullToSmallPatchMap, nextItem(this.patchedSection) + ); + if (collectedIndices.contains(fullPatchedItemIndex)) { + if (this.tocSec.isElementFourByteAligned) { + smallPatchOffset = SizeOf.roundToTimesOfFour(smallPatchOffset); + } + + int fullPatchedOffset = getItemIndexOrOffset( + fullPatchedItemInSmallPatch, fullPatchedItemIndex + ); + + if (fullPatchedItemIndex != smallPatchedIndex + || fullPatchedOffset != smallPatchOffset) { + updateIndexOrOffset( + this.fullToSmallPatchMap, + fullPatchedItemIndex, + fullPatchedOffset, + smallPatchedIndex, + smallPatchOffset + ); + } + + ++smallPatchedIndex; + smallPatchOffset += getItemSize(fullPatchedItemInSmallPatch); + } + } + return smallPatchOffset - smallPatchBaseOffset; + } + } + + private class RefToRefAffectedClassInsnVisitor extends InstructionVisitor { + private final Dex methodOwner; + private final ClassData.Method method; + private final Collection refAffectedClassDefs; + private boolean isMethodReferencedToRefAffectedClass; + + RefToRefAffectedClassInsnVisitor(Dex methodOwner, ClassData.Method method, Collection refAffectedClassDefs) { + super(null); + this.methodOwner = methodOwner; + this.method = method; + this.refAffectedClassDefs = refAffectedClassDefs; + this.isMethodReferencedToRefAffectedClass = false; + } + + @Override + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + processIndexByType(index, indexType); + } + + @Override + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + processIndexByType(index, indexType); + } + + @Override + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + processIndexByType(index, indexType); + } + + @Override + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + processIndexByType(index, indexType); + } + + @Override + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + processIndexByType(index, indexType); + } + + @Override + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + processIndexByType(index, indexType); + } + + @Override + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + processIndexByType(index, indexType); + } + + private void processIndexByType(int index, int indexType) { + String typeName = null; + String refInfoInLog = null; + switch (indexType) { + case InstructionCodec.INDEX_TYPE_TYPE_REF: { + typeName = methodOwner.typeNames().get(index); + refInfoInLog = "init ref-changed class"; + break; + } + case InstructionCodec.INDEX_TYPE_FIELD_REF: { + final FieldId fieldId = methodOwner.fieldIds().get(index); + typeName = methodOwner.typeNames().get(fieldId.declaringClassIndex); + refInfoInLog = "referencing to field: " + methodOwner.strings().get(fieldId.nameIndex); + break; + } + case InstructionCodec.INDEX_TYPE_METHOD_REF: { + final MethodId methodId = methodOwner.methodIds().get(index); + typeName = methodOwner.typeNames().get(methodId.declaringClassIndex); + refInfoInLog = "invoking method: " + getMethodProtoTypeStr(methodId); + break; + } + } + if (typeName != null && refAffectedClassDefs.contains(typeName)) { + MethodId methodId = methodOwner.methodIds().get(method.methodIndex); + logger.i( + TAG, + "Method %s in class %s referenced ref-changed class %s by %s", + getMethodProtoTypeStr(methodId), + methodOwner.typeNames().get(methodId.declaringClassIndex), + typeName, + refInfoInLog + ); + isMethodReferencedToRefAffectedClass = true; + } + } + + private String getMethodProtoTypeStr(MethodId methodId) { + StringBuilder strBuilder = new StringBuilder(); + strBuilder.append(methodOwner.strings().get(methodId.nameIndex)); + ProtoId protoId = methodOwner.protoIds().get(methodId.protoIndex); + strBuilder.append('('); + short[] paramTypeIds = methodOwner.parameterTypeIndicesFromMethodId(methodId); + for (short typeId : paramTypeIds) { + strBuilder.append(methodOwner.typeNames().get(typeId)); + } + strBuilder.append(')').append(methodOwner.typeNames().get(protoId.returnTypeIndex)); + return strBuilder.toString(); + } + } + + private class IndicesCollectorInsnVisitor extends InstructionVisitor { + private final Dex ownerDex; + private final OffsetToIndexConverter offsetToIndexConverter; + + IndicesCollectorInsnVisitor( + Dex ownerDex, OffsetToIndexConverter offsetToIndexConverter + ) { + super(null); + this.ownerDex = ownerDex; + this.offsetToIndexConverter = offsetToIndexConverter; + } + + @Override + public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { + processIndexByType(index, indexType); + } + + @Override + public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { + processIndexByType(index, indexType); + } + + @Override + public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { + processIndexByType(index, indexType); + } + + @Override + public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { + processIndexByType(index, indexType); + } + + @Override + public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { + processIndexByType(index, indexType); + } + + @Override + public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { + processIndexByType(index, indexType); + } + + @Override + public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { + processIndexByType(index, indexType); + } + + private void processIndexByType(int index, int indexType) { + switch (indexType) { + case InstructionCodec.INDEX_TYPE_STRING_REF: { + collectItemIndicesFromStringIndex( + ownerDex, index, offsetToIndexConverter + ); + break; + } + case InstructionCodec.INDEX_TYPE_TYPE_REF: { + collectItemIndicesFromTypeIndex( + ownerDex, index, offsetToIndexConverter + ); + break; + } + case InstructionCodec.INDEX_TYPE_FIELD_REF: { + collectItemIndicesFromFieldIndex( + ownerDex, index, offsetToIndexConverter + ); + break; + } + case InstructionCodec.INDEX_TYPE_METHOD_REF: { + collectItemIndicesFromMethodIndex( + ownerDex, index, offsetToIndexConverter + ); + break; + } + } + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/InfoWriter.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/InfoWriter.java new file mode 100644 index 00000000..98ecb677 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/InfoWriter.java @@ -0,0 +1,99 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.info; + +import com.tencent.tinker.build.patch.Configuration; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +/** + * Created by zhangshaowen on 16/3/8. + */ +public class InfoWriter { + protected final Configuration config; + /** + * infoFile, output info + */ + protected final String infoPath; + protected final File infoFile; + + /** + * 首次使用时初始化 + */ + protected Writer infoWrite; + + public InfoWriter(Configuration config, String infoPath) throws IOException { + this.config = config; + this.infoPath = infoPath; + + if (infoPath != null) { + this.infoFile = new File(infoPath); + if (!infoFile.getParentFile().exists()) { + infoFile.getParentFile().mkdirs(); + } + } else { + this.infoFile = null; + } + + } + + public Configuration getConfig() { + return config; + } + + + public void writeLinesToInfoFile(List lines) throws IOException { + for (String line : lines) { + writeLineToInfoFile(line); + } + } + + public void writeLineToInfoFile(String line) { + if (infoPath == null || line == null || line.length() == 0) { + return; + } + try { + checkWriter(); + infoWrite.write(line); + infoWrite.write("\n"); + infoWrite.flush(); + } catch (Exception e) { + throw new RuntimeException("write info file error, infoPath:" + infoPath + " content:" + line, e); + } + } + + private void checkWriter() throws IOException { + if (infoWrite == null) { + this.infoWrite = new BufferedWriter(new FileWriter(infoFile, false)); + } + + } + + public void close() { + try { + if (infoWrite != null) infoWrite.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfo.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfo.java new file mode 100644 index 00000000..2adf51a4 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfo.java @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.info; + +import com.tencent.tinker.build.patch.Configuration; + +/** + * Created by zhangshaowen on 16/3/8. + */ +public class PatchInfo { + + private final Configuration config; + + private final PatchInfoGen infoGen; + + + public PatchInfo(Configuration config) { + this.config = config; + infoGen = new PatchInfoGen(config); + } + + + /** + * gen the meta file txt + * such as rev, version ... + * file version, hotpatch version class + */ + public void gen() throws Exception { + infoGen.gen(); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfoGen.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfoGen.java new file mode 100644 index 00000000..a097f658 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfoGen.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.info; + +import com.tencent.tinker.build.apkparser.AndroidManifest; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.ParseException; +import java.util.Properties; + +/** + * Created by zhangshaowen on 16/3/8. + */ +public class PatchInfoGen { + private final Configuration config; + private final File packageInfoFile; + + public PatchInfoGen(Configuration config) { + this.config = config; + packageInfoFile = new File(config.mTempResultDir + File.separator + "assets" + File.separator + TypedValue.PACKAGE_META_FILE); + } + + private void addTinkerID() throws IOException, ParseException { + if (!config.mPackageFields.containsKey(TypedValue.TINKER_ID)) { + AndroidManifest oldAndroidManifest = AndroidManifest.getAndroidManifest(config.mOldApkFile); + String tinkerID = oldAndroidManifest.metaDatas.get(TypedValue.TINKER_ID); + + if (tinkerID == null) { + throw new TinkerPatchException("can't find TINKER_ID from the old apk manifest file, it must be set!"); + } + config.mPackageFields.put(TypedValue.TINKER_ID, tinkerID); + } + + if (!config.mPackageFields.containsKey(TypedValue.NEW_TINKER_ID)) { + AndroidManifest newAndroidManifest = AndroidManifest.getAndroidManifest(config.mNewApkFile); + String tinkerID = newAndroidManifest.metaDatas.get(TypedValue.TINKER_ID); + + if (tinkerID == null) { + throw new TinkerPatchException("can't find TINKER_ID from the new apk manifest file, it must be set!"); + } + config.mPackageFields.put(TypedValue.NEW_TINKER_ID, tinkerID); + } + } + + public void gen() throws Exception { + addTinkerID(); + Properties newProperties = new Properties(); + for (String key : config.mPackageFields.keySet()) { + newProperties.put(key, config.mPackageFields.get(key)); + } + + String comment = "base package config field"; + newProperties.store(new FileOutputStream(packageInfoFile, false), comment); + + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Configuration.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Configuration.java new file mode 100644 index 00000000..9b359eb5 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Configuration.java @@ -0,0 +1,576 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.patch; + +import com.tencent.tinker.build.util.FileOperation; +import com.tencent.tinker.build.util.TinkerPatchException; +import com.tencent.tinker.build.util.TypedValue; +import com.tencent.tinker.build.util.Utils; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * @author zhangshaowen + * do not use Logger here + */ +public class Configuration { + + protected static final String TAG_ISSUE = "issue"; + protected static final String DEX_ISSUE = "dex"; + protected static final String SO_ISSUE = "lib"; + protected static final String RES_ISSUE = "resource"; + + protected static final String SIGN_ISSUE = "sign"; + protected static final String PACKAGE_CONFIG_ISSUE = "packageConfig"; + protected static final String PROPERTY_ISSUE = "property"; + + protected static final String ATTR_ID = "id"; + protected static final String ATTR_VALUE = "value"; + protected static final String ATTR_NAME = "name"; + + protected static final String ATTR_IGNORE_WARNING = "ignoreWarning"; + protected static final String ATTR_USE_SIGN = "useSign"; + protected static final String ATTR_SEVEN_ZIP_PATH = "sevenZipPath"; + protected static final String ATTR_DEX_MODE = "dexMode"; + protected static final String ATTR_PATTERN = "pattern"; + protected static final String ATTR_RES_IGNORE_CHANGE = "ignoreChange"; + protected static final String ATTR_RES_LARGE_MOD = "largeModSize"; + + protected static final String ATTR_LOADER = "loader"; + protected static final String ATTR_CONFIG_FIELD = "configField"; + + protected static final String ATTR_SIGN_FILE_PATH = "path"; + protected static final String ATTR_SIGN_FILE_KEYPASS = "keypass"; + protected static final String ATTR_SIGN_FILE_STOREPASS = "storepass"; + protected static final String ATTR_SIGN_FILE_ALIAS = "alias"; + /** + * base config data + */ + public String mOldApkPath; + public String mNewApkPath; + public String mOutFolder; + public File mOldApkFile; + public File mNewApkFile; + public boolean mIgnoreWarning; + /** + * lib config + */ + public HashSet mSoFilePattern; + /** + * dex config + */ + public HashSet mDexFilePattern; + public HashSet mDexLoaderPattern; + public boolean mDexRaw; + /** + * resource config + */ + public HashSet mResFilePattern; + public HashSet mResIgnoreChangePattern; + public HashSet mResRawPattern; + public int mLargeModSize; + /** + * only gradle have the param + */ + public boolean mUseApplyResource; + + /** + * package file config + */ + public HashMap mPackageFields; + /** + * sevenZip path config + */ + public String mSevenZipPath; + /** + * sign data + */ + public boolean mUseSignAPk; + public File mSignatureFile; + public String mKeyPass; + public String mStoreAlias; + public String mStorePass; + + /** + * temp files + */ + public File mTempResultDir; + public File mTempUnzipOldDir; + public File mTempUnzipNewDir; + + public boolean mUsingGradle; + + + /** + * use by command line with xml config + */ + public Configuration(File config, File outputFile, File oldApkFile, File newApkFile) + throws IOException, ParserConfigurationException, SAXException, TinkerPatchException { + mUsingGradle = false; + mSoFilePattern = new HashSet<>(); + mDexFilePattern = new HashSet<>(); + mDexLoaderPattern = new HashSet<>(); + + mResFilePattern = new HashSet<>(); + mResRawPattern = new HashSet<>(); + mResIgnoreChangePattern = new HashSet<>(); + + mPackageFields = new HashMap<>(); + mOutFolder = outputFile.getAbsolutePath(); + FileOperation.cleanDir(outputFile); + + mOldApkFile = oldApkFile; + mOldApkPath = oldApkFile.getAbsolutePath(); + + mNewApkFile = newApkFile; + mNewApkPath = newApkFile.getAbsolutePath(); + mLargeModSize = 100; + readXmlConfig(config); + createTempDirectory(); + checkInputPatternParameter(); + } + + + /** + * use by gradle + */ + public Configuration(InputParam param) throws IOException, TinkerPatchException { + mUsingGradle = true; + mSoFilePattern = new HashSet<>(); + mDexFilePattern = new HashSet<>(); + mDexLoaderPattern = new HashSet<>(); + + mResFilePattern = new HashSet<>(); + mResRawPattern = new HashSet<>(); + mResIgnoreChangePattern = new HashSet<>(); + + mPackageFields = new HashMap<>(); + + for (String item : param.soFilePattern) { + addToPatterns(item, mSoFilePattern); + } + + for (String item : param.dexFilePattern) { + addToPatterns(item, mDexFilePattern); + } + + for (String item : param.resourceFilePattern) { + mResRawPattern.add(item); + addToPatterns(item, mResFilePattern); + } + + for (String item : param.resourceIgnoreChangePattern) { + addToPatterns(item, mResIgnoreChangePattern); + } + mLargeModSize = param.largeModSize; + //only gradle have the param + mUseApplyResource = param.useApplyResource; + + mDexLoaderPattern.addAll(param.dexLoaderPattern); + + //can be only raw or jar + if (param.dexMode.equals("raw")) { + mDexRaw = true; + } + + mOldApkPath = param.oldApk; + mOldApkFile = new File(mOldApkPath); + + mNewApkPath = param.newApk; + mNewApkFile = new File(mNewApkPath); + + mOutFolder = param.outFolder; + + mIgnoreWarning = param.ignoreWarning; + mSevenZipPath = param.sevenZipPath; + mPackageFields = param.configFields; + + mUseSignAPk = param.useSign; + setSignData(param.signFile, param.keypass, param.storealias, param.storepass); + + FileOperation.cleanDir(new File(mOutFolder)); + + createTempDirectory(); + checkInputPatternParameter(); + + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("configuration: \n"); + sb.append("oldApk:" + mOldApkPath + "\n"); + sb.append("newApk:" + mNewApkPath + "\n"); + sb.append("outputFolder:" + mOutFolder + "\n"); + sb.append("isIgnoreWarning:" + mIgnoreWarning + "\n"); + sb.append("7-ZipPath:" + mSevenZipPath + "\n"); + sb.append("useSignAPk:" + mUseSignAPk + "\n"); + + sb.append("package meta fields: \n"); + + for (String name : mPackageFields.keySet()) { + sb.append("filed name:" + name + ", filed value:" + mPackageFields.get(name) + "\n"); + } + + sb.append("dex configs: \n"); + if (mDexRaw) { + sb.append("dexMode: raw" + "\n"); + } else { + sb.append("dexMode: jar" + "\n"); + } + for (Pattern name : mDexFilePattern) { + sb.append("dexPattern:" + name.toString() + "\n"); + } + for (String name : mDexLoaderPattern) { + sb.append("dex loader:" + name + "\n"); + } + + sb.append("lib configs: \n"); + for (Pattern name : mSoFilePattern) { + sb.append("libPattern:" + name.toString() + "\n"); + } + + sb.append("resource configs: \n"); + for (Pattern name : mResFilePattern) { + sb.append("resPattern:" + name.toString() + "\n"); + } + for (Pattern name : mResIgnoreChangePattern) { + sb.append("resIgnore change:" + name.toString() + "\n"); + } + sb.append("largeModSize:" + mLargeModSize + "kb\n"); + sb.append("useApplyResource:" + mUseApplyResource + "\n"); + return sb.toString(); + } + + private void createTempDirectory() throws TinkerPatchException { + mTempResultDir = new File(mOutFolder + File.separator + TypedValue.PATH_PATCH_FILES); + FileOperation.deleteDir(mTempResultDir); + if (!mTempResultDir.exists()) { + mTempResultDir.mkdir(); + } + + String oldApkName = mOldApkFile.getName(); + if (!oldApkName.endsWith(TypedValue.FILE_APK)) { + throw new TinkerPatchException( + String.format("input apk file path must end with .apk, yours %s\n", oldApkName) + ); + } + + String newApkName = mNewApkFile.getName(); + if (!newApkName.endsWith(TypedValue.FILE_APK)) { + throw new TinkerPatchException( + String.format("input apk file path must end with .apk, yours %s\n", newApkName) + ); + } + + String tempOldName = oldApkName.substring(0, oldApkName.indexOf(TypedValue.FILE_APK)); + + + String tempNewName = newApkName.substring(0, newApkName.indexOf(TypedValue.FILE_APK)); + + if (tempNewName.equals(tempOldName)) { + tempOldName += "-old"; + tempNewName += "-new"; + } + + mTempUnzipOldDir = new File(mOutFolder, tempOldName); + mTempUnzipNewDir = new File(mOutFolder, tempNewName); + } + + public void setSignData(File signatureFile, String keypass, String storealias, String storepass) throws IOException { + if (mUseSignAPk) { + mSignatureFile = signatureFile; + if (!mSignatureFile.exists()) { + throw new IOException( + String.format("the signature file do not exit, raw path= %s\n", mSignatureFile.getAbsolutePath()) + ); + } + mKeyPass = keypass; + mStoreAlias = storealias; + mStorePass = storepass; + } + } + + private void checkInputPatternParameter() throws TinkerPatchException { + if (mSoFilePattern.isEmpty() && mDexFilePattern.isEmpty() && mResFilePattern.isEmpty()) { + throw new TinkerPatchException("no dex, so or resource pattern are found"); + } + if (mLargeModSize <= 0) { + throw new TinkerPatchException("largeModSize must be larger than 0"); + } + + } + + /** + * read args from xml + **/ + void readXmlConfig(File xmlConfigFile) + throws IOException, ParserConfigurationException, SAXException { + if (!xmlConfigFile.exists()) { + return; + } + + System.out.printf("reading config file, %s\n", xmlConfigFile.getAbsolutePath()); + BufferedInputStream input = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + input = new BufferedInputStream(new FileInputStream(xmlConfigFile)); + InputSource source = new InputSource(input); + factory.setNamespaceAware(false); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(source); + NodeList issues = document.getElementsByTagName(TAG_ISSUE); + for (int i = 0, count = issues.getLength(); i < count; i++) { + Node node = issues.item(i); + + Element element = (Element) node; + String id = element.getAttribute(ATTR_ID); + if (id.length() == 0) { + System.err.println("Invalid config file: Missing required issue id attribute"); + continue; + } + if (id.equals(PROPERTY_ISSUE)) { + readPropertyFromXml(node); + } else if (id.equals(DEX_ISSUE)) { + readDexPatternsFromXml(node); + } else if (id.equals(SO_ISSUE)) { + readLibPatternsFromXml(node); + } else if (id.equals(RES_ISSUE)) { + readResPatternsFromXml(node); + } else if (id.equals(PACKAGE_CONFIG_ISSUE)) { + readPackageConfigFromXml(node); + } else if (id.equals(SIGN_ISSUE)) { + if (mUseSignAPk) { + readSignFromXml(node); + } + } else { + System.err.println("unknown issue " + id); + } + } + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + System.exit(-1); + } + } + } + } + + private void readPropertyFromXml(Node node) throws IOException { + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + String value = check.getAttribute(ATTR_VALUE); + if (value.length() == 0) { + throw new IOException( + String.format("Invalid config file: Missing required attribute %s\n", ATTR_VALUE) + ); + } + if (tagName.equals(ATTR_IGNORE_WARNING)) { + mIgnoreWarning = value.equals("true"); + } else if (tagName.equals(ATTR_USE_SIGN)) { + mUseSignAPk = value.equals("true"); + } else if (tagName.equals(ATTR_SEVEN_ZIP_PATH)) { + File sevenZipFile = new File(value); + if (sevenZipFile.exists()) { + mSevenZipPath = value; + } else { + mSevenZipPath = "7za"; + } + } else { + System.err.println("unknown property tag " + tagName); + } + } + } + } + } + + + private void readSignFromXml(Node node) throws IOException { + if (mSignatureFile != null) { + System.err.println("already set the sign info from command line, ignore this"); + return; + } + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + String value = check.getAttribute(ATTR_VALUE); + if (value.length() == 0) { + throw new IOException( + String.format("Invalid config file: Missing required attribute %s\n", ATTR_VALUE) + ); + } + + if (tagName.equals(ATTR_SIGN_FILE_PATH)) { + mSignatureFile = new File(value); + if (!mSignatureFile.exists()) { + throw new IOException( + String.format("the signature file do not exit, raw path= %s\n", mSignatureFile.getAbsolutePath()) + ); + } + } else if (tagName.equals(ATTR_SIGN_FILE_STOREPASS)) { + mStorePass = value; + mStorePass = mStorePass.trim(); + } else if (tagName.equals(ATTR_SIGN_FILE_KEYPASS)) { + mKeyPass = value; + mKeyPass = mKeyPass.trim(); + } else if (tagName.equals(ATTR_SIGN_FILE_ALIAS)) { + mStoreAlias = value; + mStoreAlias = mStoreAlias.trim(); + } else { + System.err.println("unknown sign tag " + tagName); + } + } + } + } + + } + + private void readDexPatternsFromXml(Node node) throws IOException { + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + + String value = check.getAttribute(ATTR_VALUE); + if (tagName.equals(ATTR_DEX_MODE)) { + if (value.equals("raw")) { + mDexRaw = true; + } + } else if (tagName.equals(ATTR_PATTERN)) { + addToPatterns(value, mDexFilePattern); + } else if (tagName.equals(ATTR_LOADER)) { + mDexLoaderPattern.add(value); + } else { + System.err.println("unknown dex tag " + tagName); + } + } + } + } + } + + private void readLibPatternsFromXml(Node node) throws IOException { + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + + String value = check.getAttribute(ATTR_VALUE); + if (tagName.equals(ATTR_PATTERN)) { + addToPatterns(value, mSoFilePattern); + } else { + System.err.println("unknown dex tag " + tagName); + } + } + } + } + } + + private void readResPatternsFromXml(Node node) throws IOException { + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + + String value = check.getAttribute(ATTR_VALUE); + if (tagName.equals(ATTR_PATTERN)) { + mResRawPattern.add(value); + addToPatterns(value, mResFilePattern); + } else if (tagName.equals(ATTR_RES_IGNORE_CHANGE)) { + addToPatterns(value, mResIgnoreChangePattern); + } else if (tagName.equals(ATTR_RES_LARGE_MOD)) { + mLargeModSize = Integer.valueOf(value); + } else { + System.err.println("unknown dex tag " + tagName); + } + } + } + } + } + + private void readPackageConfigFromXml(Node node) throws IOException { + NodeList childNodes = node.getChildNodes(); + if (childNodes.getLength() > 0) { + for (int j = 0, n = childNodes.getLength(); j < n; j++) { + Node child = childNodes.item(j); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element check = (Element) child; + String tagName = check.getTagName(); + + String value = check.getAttribute(ATTR_VALUE); + String name = check.getAttribute(ATTR_NAME); + + if (tagName.equals(ATTR_CONFIG_FIELD)) { + mPackageFields.put(name, value); + } else { + System.err.println("unknown package config tag " + tagName); + } + } + } + } + } + + private void addToPatterns(String value, HashSet patterns) throws IOException { + if (value.length() == 0) { + throw new IOException( + String.format("Invalid config file: Missing required attribute %s\n", ATTR_VALUE) + ); + } + value = Utils.convertToPatternString(value); + Pattern pattern = Pattern.compile(value); + patterns.add(pattern); + } + +} + diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/InputParam.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/InputParam.java new file mode 100644 index 00000000..6a217f47 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/InputParam.java @@ -0,0 +1,295 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.patch; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by zhangshaowen on 1/9/16. + */ +public class InputParam { + /** + * tinkerPatch + */ + public final String oldApk; + public final String newApk; + public final String outFolder; + public final File signFile; + public final String keypass; + public final String storealias; + public final String storepass; + public final boolean ignoreWarning; + public final boolean useSign; + + /** + * tinkerPatch.dex + */ + public final ArrayList dexFilePattern; + public final ArrayList dexLoaderPattern; + public final String dexMode; + /** + * tinkerPatch.lib + */ + public final ArrayList soFilePattern; + /** + * tinkerPatch.resource pattern + */ + public final ArrayList resourceFilePattern; + /** + * tinkerPath.resource ignoreChange + */ + public final ArrayList resourceIgnoreChangePattern; + /** + * tinkerPath.resource largeModSize + */ + public final int largeModSize; + /** + * tinkerPath.buildConfig applyResourceMapping + */ + public final boolean useApplyResource; + /** + * tinkerPatch.packageConfig + */ + public final HashMap configFields; + /** + * tinkerPatch.sevenZip + */ + public final String sevenZipPath; + + private InputParam( + String oldApk, + String newApk, + String outFolder, + File signFile, + String keypass, + String storealias, + String storepass, + boolean ignoreWarning, + boolean useSign, + + ArrayList dexFilePattern, + ArrayList dexLoaderPattern, + String dexMode, + ArrayList soFilePattern, + ArrayList resourceFilePattern, + ArrayList resourceIgnoreChangePattern, + int largeModSize, + boolean useApplyResource, + HashMap configFields, + + String sevenZipPath + ) { + this.oldApk = oldApk; + this.newApk = newApk; + this.outFolder = outFolder; + this.signFile = signFile; + this.keypass = keypass; + this.storealias = storealias; + this.storepass = storepass; + this.ignoreWarning = ignoreWarning; + this.useSign = useSign; + + this.dexFilePattern = dexFilePattern; + this.dexLoaderPattern = dexLoaderPattern; + this.dexMode = dexMode; + + this.soFilePattern = soFilePattern; + this.resourceFilePattern = resourceFilePattern; + this.resourceIgnoreChangePattern = resourceIgnoreChangePattern; + this.largeModSize = largeModSize; + this.useApplyResource = useApplyResource; + + this.configFields = configFields; + + this.sevenZipPath = sevenZipPath; + } + + public static class Builder { + /** + * tinkerPatch + */ + private String oldApk; + private String newApk; + private String outFolder; + private File signFile; + private String keypass; + private String storealias; + private String storepass; + private boolean ignoreWarning; + private boolean useSign; + + /** + * tinkerPatch.dex + */ + private ArrayList dexFilePattern; + private ArrayList dexLoaderPattern; + private String dexMode; + /** + * tinkerPatch.lib + */ + private ArrayList soFilePattern; + /** + * tinkerPath.resource pattern + */ + private ArrayList resourceFilePattern; + /** + * tinkerPath.resource ignoreChange + */ + private ArrayList resourceIgnoreChangePattern; + /** + * tinkerPath.resource largeModSize + */ + private int largeModSize; + /** + * tinkerPath.buildConfig applyResourceMapping + */ + private boolean useApplyResource; + /** + * tinkerPatch.packageConfig + */ + private HashMap configFields; + /** + * tinkerPatch.sevenZip + */ + private String sevenZipPath; + + + public Builder() { + } + + public Builder setOldApk(String oldApk) { + this.oldApk = oldApk; + return this; + } + + public Builder setNewApk(String newApk) { + this.newApk = newApk; + return this; + } + + public Builder setSoFilePattern(ArrayList soFilePattern) { + this.soFilePattern = soFilePattern; + return this; + } + + public Builder setResourceFilePattern(ArrayList resourceFilePattern) { + this.resourceFilePattern = resourceFilePattern; + return this; + } + + public Builder setResourceIgnoreChangePattern(ArrayList resourceIgnoreChangePattern) { + this.resourceIgnoreChangePattern = resourceIgnoreChangePattern; + return this; + } + + public Builder setResourceLargeModSize(int largeModSize) { + this.largeModSize = largeModSize; + return this; + } + + public Builder setUseApplyResource(boolean useApplyResource) { + this.useApplyResource = useApplyResource; + return this; + } + + public Builder setDexFilePattern(ArrayList dexFilePattern) { + this.dexFilePattern = dexFilePattern; + return this; + } + + public Builder setOutBuilder(String outFolder) { + this.outFolder = outFolder; + return this; + } + + public Builder setSignFile(File signFile) { + this.signFile = signFile; + return this; + } + + public Builder setKeypass(String keypass) { + this.keypass = keypass; + return this; + } + + public Builder setStorealias(String storealias) { + this.storealias = storealias; + return this; + } + + public Builder setStorepass(String storepass) { + this.storepass = storepass; + return this; + } + + public Builder setIgnoreWarning(boolean ignoreWarning) { + this.ignoreWarning = ignoreWarning; + return this; + } + + public Builder setDexLoaderPattern(ArrayList dexLoaderPattern) { + this.dexLoaderPattern = dexLoaderPattern; + return this; + } + + public Builder setDexMode(String dexMode) { + this.dexMode = dexMode; + return this; + } + + public Builder setConfigFields(HashMap configFields) { + this.configFields = configFields; + return this; + } + + public Builder setSevenZipPath(String sevenZipPath) { + this.sevenZipPath = sevenZipPath; + return this; + } + + public Builder setUseSign(boolean useSign) { + this.useSign = useSign; + return this; + } + + public InputParam create() { + return new InputParam( + oldApk, + newApk, + outFolder, + signFile, + keypass, + storealias, + storepass, + ignoreWarning, + useSign, + dexFilePattern, + dexLoaderPattern, + dexMode, + soFilePattern, + resourceFilePattern, + resourceIgnoreChangePattern, + largeModSize, + useApplyResource, + configFields, + sevenZipPath + ); + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Runner.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Runner.java new file mode 100644 index 00000000..78648b06 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Runner.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.patch; + +import com.tencent.tinker.build.builder.PatchBuilder; +import com.tencent.tinker.build.decoder.ApkDecoder; +import com.tencent.tinker.build.info.PatchInfo; +import com.tencent.tinker.build.util.Logger; +import com.tencent.tinker.build.util.TinkerPatchException; + +import java.io.IOException; + +/** + * Created by zhangshaowen on 2/26/16. + */ +public class Runner { + + public static final int ERRNO_ERRORS = 1; + public static final int ERRNO_USAGE = 2; + + protected static long mBeginTime; + protected Configuration config; + + public static void gradleRun(InputParam inputParam) { + mBeginTime = System.currentTimeMillis(); + Runner m = new Runner(); + m.run(inputParam); + } + + private void run(InputParam inputParam) { + loadConfigFromGradle(inputParam); + try { + Logger.initLogger(config); + tinkerPatch(); + } catch (IOException e) { + e.printStackTrace(); + goToError(); + } finally { + Logger.closeLogger(); + } + } + + protected void tinkerPatch() { + Logger.d("-----------------------Tinker patch begin-----------------------"); + + Logger.d(config.toString()); + try { + //gen patch + ApkDecoder decoder = new ApkDecoder(config); + decoder.onAllPatchesStart(); + decoder.patch(config.mOldApkFile, config.mNewApkFile); + decoder.onAllPatchesEnd(); + + //gen meta file and version file + PatchInfo info = new PatchInfo(config); + info.gen(); + + //build patch + PatchBuilder builder = new PatchBuilder(config); + builder.buildPatch(); + + } catch (Throwable e) { + e.printStackTrace(); + goToError(); + } + + Logger.d("Tinker patch done, total time cost: %fs", diffTimeFromBegin()); + Logger.d("Tinker patch done, you can go to file to find the output %s", config.mOutFolder); + Logger.d("-----------------------Tinker patch end-------------------------"); + } + + private void loadConfigFromGradle(InputParam inputParam) { + try { + config = new Configuration(inputParam); + } catch (IOException e) { + e.printStackTrace(); + } catch (TinkerPatchException e) { + e.printStackTrace(); + } + } + + public void goToError() { + System.exit(ERRNO_USAGE); + } + + public double diffTimeFromBegin() { + long end = System.currentTimeMillis(); + return (end - mBeginTime) / 1000.0; + } + +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/DexClassesComparator.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/DexClassesComparator.java new file mode 100644 index 00000000..fe55d9a9 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/DexClassesComparator.java @@ -0,0 +1,1534 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassData.Field; +import com.tencent.tinker.android.dex.ClassData.Method; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.EncodedValueReader; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.instruction.InstructionComparator; +import com.tencent.tinker.build.dexpatcher.util.PatternUtils; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; +import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger.IDexPatcherLogger; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Created by tangyinsheng on 2016/4/14. + */ +public final class DexClassesComparator { + public static final int COMPARE_MODE_NORMAL = 0; + public static final int COMPARE_MODE_CAUSE_REF_CHANGE_ONLY = 1; + private static final String TAG = "DexClassesComparator"; + private static final int DBG_FIRST_SPECIAL = 0x0A; // the smallest special opcode + private static final int DBG_LINE_BASE = -4; // the smallest line number increment + private static final int DBG_LINE_RANGE = 15; // the number of line increments represented + private final List addedClassInfoList = new ArrayList<>(); + private final List deletedClassInfoList = new ArrayList<>(); + // classDesc => [oldClassInfo, newClassInfo] + private final Map changedClassDescToClassInfosMap = new HashMap<>(); + private final Set patternsOfClassDescToCheck = new HashSet<>(); + private final Set patternsOfIgnoredRemovedClassDesc = new HashSet<>(); + private final Set oldDescriptorOfClassesToCheck = new HashSet<>(); + private final Set newDescriptorOfClassesToCheck = new HashSet<>(); + private final Map oldClassDescriptorToClassInfoMap = new HashMap<>(); + private final Map newClassDescriptorToClassInfoMap = new HashMap<>(); + // Record class descriptors whose references key (index or offset) of methods and fields + // are changed. + private final Set refAffectedClassDescs = new HashSet<>(); + private final DexPatcherLogger logger = new DexPatcherLogger(); + private int compareMode = COMPARE_MODE_NORMAL; + + public DexClassesComparator(String patternStringOfClassDescToCheck) { + patternsOfClassDescToCheck.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStringOfClassDescToCheck) + ) + ); + } + + public DexClassesComparator(String... patternStringsOfClassDescToCheck) { + for (String patternStr : patternStringsOfClassDescToCheck) { + patternsOfClassDescToCheck.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) + ) + ); + } + } + + public DexClassesComparator(Collection patternStringsOfClassDescToCheck) { + for (String patternStr : patternStringsOfClassDescToCheck) { + patternsOfClassDescToCheck.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) + ) + ); + } + } + + public void setIgnoredRemovedClassDescPattern(String... patternStringsOfLoaderClassDesc) { + patternsOfIgnoredRemovedClassDesc.clear(); + for (String patternStr : patternStringsOfLoaderClassDesc) { + patternsOfIgnoredRemovedClassDesc.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) + ) + ); + } + } + + public void setIgnoredRemovedClassDescPattern(Collection patternStringsOfLoaderClassDesc) { + patternsOfIgnoredRemovedClassDesc.clear(); + for (String patternStr : patternStringsOfLoaderClassDesc) { + patternsOfIgnoredRemovedClassDesc.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) + ) + ); + } + } + + public void setCompareMode(int mode) { + if (mode == COMPARE_MODE_NORMAL || mode == COMPARE_MODE_CAUSE_REF_CHANGE_ONLY) { + this.compareMode = mode; + } else { + throw new IllegalArgumentException("bad compare mode: " + mode); + } + } + + public void setLogger(IDexPatcherLogger logger) { + this.logger.setLoggerImpl(logger); + } + + public List getAddedClassInfos() { + return Collections.unmodifiableList(addedClassInfoList); + } + + public List getDeletedClassInfos() { + return Collections.unmodifiableList(deletedClassInfoList); + } + + public Map getChangedClassDescToInfosMap() { + return Collections.unmodifiableMap(changedClassDescToClassInfosMap); + } + + public void startCheck(File oldDexFile, File newDexFile) throws IOException { + startCheck(new Dex(oldDexFile), new Dex(newDexFile)); + } + + public void startCheck(Dex oldDex, Dex newDex) { + startCheck(DexGroup.wrap(oldDex), DexGroup.wrap(newDex)); + } + + public void startCheck(DexGroup oldDexGroup, DexGroup newDexGroup) { + // Init assist structures. + addedClassInfoList.clear(); + deletedClassInfoList.clear(); + changedClassDescToClassInfosMap.clear(); + oldDescriptorOfClassesToCheck.clear(); + newDescriptorOfClassesToCheck.clear(); + oldClassDescriptorToClassInfoMap.clear(); + newClassDescriptorToClassInfoMap.clear(); + refAffectedClassDescs.clear(); + + // Map classDesc and typeIndex to classInfo + // and collect typeIndex of classes to check in oldDexes. + for (Dex oldDex : oldDexGroup.dexes) { + int classDefIndex = 0; + for (ClassDef oldClassDef : oldDex.classDefs()) { + String desc = oldDex.typeNames().get(oldClassDef.typeIndex); + if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { + if (!oldDescriptorOfClassesToCheck.add(desc)) { + throw new IllegalStateException( + String.format( + "duplicate class descriptor [%s] in different old dexes.", + desc + ) + ); + } + } + DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, oldClassDef, oldDex); + ++classDefIndex; + oldClassDescriptorToClassInfoMap.put(desc, classInfo); + } + } + + // Map classDesc and typeIndex to classInfo + // and collect typeIndex of classes to check in newDexes. + for (Dex newDex : newDexGroup.dexes) { + int classDefIndex = 0; + for (ClassDef newClassDef : newDex.classDefs()) { + String desc = newDex.typeNames().get(newClassDef.typeIndex); + if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { + if (!newDescriptorOfClassesToCheck.add(desc)) { + throw new IllegalStateException( + String.format( + "duplicate class descriptor [%s] in different new dexes.", + desc + ) + ); + } + } + DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, newClassDef, newDex); + ++classDefIndex; + newClassDescriptorToClassInfoMap.put(desc, classInfo); + } + } + + Set deletedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck); + deletedClassDescs.removeAll(newDescriptorOfClassesToCheck); + + for (String desc : deletedClassDescs) { + // These classes are deleted as we expect to, so we remove them + // from result. + if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) { + logger.i(TAG, "Ignored deleted class: %s", desc); + continue; + } else { + logger.i(TAG, "Deleted class: %s", desc); + } + deletedClassInfoList.add(oldClassDescriptorToClassInfoMap.get(desc)); + } + + Set addedClassDescs = new HashSet<>(newDescriptorOfClassesToCheck); + addedClassDescs.removeAll(oldDescriptorOfClassesToCheck); + + for (String desc : addedClassDescs) { + logger.i(TAG, "Added class: %s", desc); + addedClassInfoList.add(newClassDescriptorToClassInfoMap.get(desc)); + } + + Set mayBeChangedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck); + mayBeChangedClassDescs.retainAll(newDescriptorOfClassesToCheck); + + for (String desc : mayBeChangedClassDescs) { + DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(desc); + DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(desc); + switch (compareMode) { + case COMPARE_MODE_NORMAL: { + if (!isSameClass( + oldClassInfo.owner, + newClassInfo.owner, + oldClassInfo.classDef, + newClassInfo.classDef + )) { + logger.i(TAG, "Changed class: %s", desc); + changedClassDescToClassInfosMap.put( + desc, new DexClassInfo[]{oldClassInfo, newClassInfo} + ); + } + break; + } + case COMPARE_MODE_CAUSE_REF_CHANGE_ONLY: { + if (isClassChangeAffectedToRef( + oldClassInfo.owner, + newClassInfo.owner, + oldClassInfo.classDef, + newClassInfo.classDef + )) { + logger.i(TAG, "Ref-changed class: %s", desc); + changedClassDescToClassInfosMap.put( + desc, new DexClassInfo[]{oldClassInfo, newClassInfo} + ); + } + break; + } + } + } + } + + private boolean isClassChangeAffectedToRef( + Dex oldDex, + Dex newDex, + ClassDef oldClassDef, + ClassDef newClassDef + ) { + boolean result = false; + + String classDesc = oldDex.typeNames().get(oldClassDef.typeIndex); + + do { + if (refAffectedClassDescs.contains(classDesc)) { + result = true; + return result; + } + + // Any changes on superclass could affect refs of members in current class. + if (isTypeChangeAffectedToRef( + oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex + )) { + result = true; + break; + } + + // Any changes on current class's interface list could affect refs + // of members in current class. + short[] oldInterfaceTypeIds = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef); + short[] newInterfaceTypeIds = newDex.interfaceTypeIndicesFromClassDef(newClassDef); + if (isTypeIdsChangeAffectedToRef( + oldDex, newDex, oldInterfaceTypeIds, newInterfaceTypeIds, false + )) { + result = true; + break; + } + + // Any changes on current class's member lists could affect refs + // of members in current class. + ClassData oldClassData = + (oldClassDef.classDataOffset != 0 ? oldDex.readClassData(oldClassDef) : null); + ClassData newClassData = + (newClassDef.classDataOffset != 0 ? newDex.readClassData(newClassDef) : null); + if (isClassDataChangeAffectedToRef( + oldDex, newDex, oldClassData, newClassData + )) { + result = true; + break; + } + } while (false); + + if (result) { + refAffectedClassDescs.add(classDesc); + } + + return result; + } + + private boolean isTypeChangeAffectedToRef( + Dex oldDex, Dex newDex, int oldTypeId, int newTypeId + ) { + if (oldTypeId != ClassDef.NO_INDEX && newTypeId != ClassDef.NO_INDEX) { + String oldClassDesc = oldDex.typeNames().get(oldTypeId); + String newClassDesc = newDex.typeNames().get(newTypeId); + if (!oldClassDesc.equals(newClassDesc)) { + return true; + } + + final DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(oldClassDesc); + final DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(newClassDesc); + ClassDef oldClassDef = (oldClassInfo != null ? oldClassInfo.classDef : null); + ClassDef newClassDef = (newClassInfo != null ? newClassInfo.classDef : null); + if (oldClassDef != null && newClassDef != null) { + return isClassChangeAffectedToRef(oldClassInfo.owner, newClassInfo.owner, oldClassDef, newClassDef); + } else + if (oldClassDef == null && newClassDef == null) { + return false; + } else { + // If current comparing class is ignored, since it must be removed + // in patched dexes as we expected, here we ignore this kind of changes. + return !Utils.isStringMatchesPatterns(oldClassDesc, patternsOfIgnoredRemovedClassDesc); + } + } else { + if (!(oldTypeId == ClassDef.NO_INDEX && newTypeId == ClassDef.NO_INDEX)) { + return true; + } + } + return false; + } + + private boolean isTypeIdsChangeAffectedToRef( + Dex oldDex, + Dex newDex, + short[] oldTypeIds, + short[] newTypeIds, + boolean compareNameOnly + ) { + if (oldTypeIds.length != newTypeIds.length) { + return true; + } + + int typeIdCount = oldTypeIds.length; + for (int i = 0; i < typeIdCount; ++i) { + if (compareNameOnly) { + String oldTypeName = oldDex.typeNames().get(oldTypeIds[i]); + String newTypeName = newDex.typeNames().get(newTypeIds[i]); + if (!oldTypeName.equals(newTypeName)) { + return true; + } + } else { + if (isTypeChangeAffectedToRef(oldDex, newDex, oldTypeIds[i], newTypeIds[i])) { + return true; + } + } + } + + return false; + } + + private boolean isClassDataChangeAffectedToRef( + Dex oldDex, + Dex newDex, + ClassData oldClassData, + ClassData newClassData + ) { + if (oldClassData != null && newClassData != null) { + if (isFieldsChangeAffectedToRef( + oldDex, newDex, oldClassData.instanceFields, newClassData.instanceFields + )) { + return true; + } + + if (isFieldsChangeAffectedToRef( + oldDex, newDex, oldClassData.staticFields, newClassData.staticFields + )) { + return true; + } + + if (isMethodsChangeAffectedToRef( + oldDex, newDex, oldClassData.directMethods, newClassData.directMethods + )) { + return true; + } + + if (isMethodsChangeAffectedToRef( + oldDex, newDex, oldClassData.virtualMethods, newClassData.virtualMethods + )) { + return true; + } + } else { + if (!(oldClassData == null && newClassData == null)) { + return true; + } + } + return false; + } + + private boolean isFieldsChangeAffectedToRef( + Dex oldDex, + Dex newDex, + Field[] oldFields, + Field[] newFields + ) { + if (oldFields.length != newFields.length) { + return true; + } + + int fieldCount = oldFields.length; + for (int i = 0; i < fieldCount; ++i) { + Field oldField = oldFields[i]; + Field newField = newFields[i]; + + if (oldField.accessFlags != newField.accessFlags) { + return true; + } + + FieldId oldFieldId = oldDex.fieldIds().get(oldField.fieldIndex); + FieldId newFieldId = newDex.fieldIds().get(newField.fieldIndex); + + String oldFieldName = oldDex.strings().get(oldFieldId.nameIndex); + String newFieldName = newDex.strings().get(newFieldId.nameIndex); + if (!oldFieldName.equals(newFieldName)) { + return true; + } + + String oldFieldTypeName = oldDex.typeNames().get(oldFieldId.typeIndex); + String newFieldTypeName = newDex.typeNames().get(newFieldId.typeIndex); + if (!oldFieldTypeName.equals(newFieldTypeName)) { + return true; + } + } + + return false; + } + + private boolean isMethodsChangeAffectedToRef( + Dex oldDex, + Dex newDex, + Method[] oldMethods, + Method[] newMethods + ) { + if (oldMethods.length != newMethods.length) { + return true; + } + + int methodCount = oldMethods.length; + for (int i = 0; i < methodCount; ++i) { + Method oldMethod = oldMethods[i]; + Method newMethod = newMethods[i]; + + if (oldMethod.accessFlags != newMethod.accessFlags) { + return true; + } + + MethodId oldMethodId = oldDex.methodIds().get(oldMethod.methodIndex); + MethodId newMethodId = newDex.methodIds().get(newMethod.methodIndex); + + String oldMethodName = oldDex.strings().get(oldMethodId.nameIndex); + String newMethodName = newDex.strings().get(newMethodId.nameIndex); + if (!oldMethodName.equals(newMethodName)) { + return true; + } + + ProtoId oldProtoId = oldDex.protoIds().get(oldMethodId.protoIndex); + ProtoId newProtoId = newDex.protoIds().get(newMethodId.protoIndex); + + String oldMethodShorty = oldDex.strings().get(oldProtoId.shortyIndex); + String newMethodShorty = newDex.strings().get(newProtoId.shortyIndex); + if (!oldMethodShorty.equals(newMethodShorty)) { + return true; + } + + String oldMethodReturnTypeName = oldDex.typeNames().get(oldProtoId.returnTypeIndex); + String newMethodReturnTypeName = newDex.typeNames().get(newProtoId.returnTypeIndex); + if (!oldMethodReturnTypeName.equals(newMethodReturnTypeName)) { + return true; + } + + short[] oldParameterIds = oldDex.parameterTypeIndicesFromMethodId(oldMethodId); + short[] newParameterIds = newDex.parameterTypeIndicesFromMethodId(newMethodId); + if (isTypeIdsChangeAffectedToRef( + oldDex, newDex, oldParameterIds, newParameterIds, true + )) { + return true; + } + } + return false; + } + + private boolean isSameClass( + Dex oldDex, + Dex newDex, + ClassDef oldClassDef, + ClassDef newClassDef + ) { + if (oldClassDef.accessFlags != newClassDef.accessFlags) { + return false; + } + + if (!isSameClassDesc( + oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex + )) { + return false; + } + + short[] oldInterfaceIndices = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef); + short[] newInterfaceIndices = newDex.interfaceTypeIndicesFromClassDef(newClassDef); + if (oldInterfaceIndices.length != newInterfaceIndices.length) { + return false; + } else { + for (int i = 0; i < oldInterfaceIndices.length; ++i) { + if (!isSameClassDesc(oldDex, newDex, oldInterfaceIndices[i], newInterfaceIndices[i])) { + return false; + } + } + } + + if (!isSameName(oldDex, newDex, oldClassDef.sourceFileIndex, newClassDef.sourceFileIndex)) { + return false; + } + + if (!isSameAnnotationDirectory( + oldDex, + newDex, + oldClassDef.annotationsOffset, + newClassDef.annotationsOffset + )) { + return false; + } + + if (!isSameClassData( + oldDex, + newDex, + oldClassDef.classDataOffset, + newClassDef.classDataOffset + )) { + return false; + } + + return isSameStaticValue( + oldDex, + newDex, + oldClassDef.staticValuesOffset, + newClassDef.staticValuesOffset + ); + } + + private boolean isSameStaticValue( + Dex oldDex, + Dex newDex, + int oldStaticValueOffset, + int newStaticValueOffset + ) { + if (oldStaticValueOffset == 0 && newStaticValueOffset == 0) { + return true; + } + + if (oldStaticValueOffset == 0 || newStaticValueOffset == 0) { + return false; + } + + EncodedValue oldStaticValue = + oldDex.openSection(oldStaticValueOffset).readEncodedArray(); + EncodedValue newStaticValue = + newDex.openSection(newStaticValueOffset).readEncodedArray(); + EncodedValueReader oldReader = + new EncodedValueReader(oldStaticValue, EncodedValueReader.ENCODED_ARRAY); + EncodedValueReader newReader = + new EncodedValueReader(newStaticValue, EncodedValueReader.ENCODED_ARRAY); + + return isSameEncodedValue(oldDex, newDex, oldReader, newReader); + } + + private boolean isSameClassDesc(Dex oldDex, Dex newDex, int oldTypeId, int newTypeId) { + String oldClassDesc = oldDex.typeNames().get(oldTypeId); + String newClassDesc = newDex.typeNames().get(newTypeId); + return oldClassDesc.equals(newClassDesc); + } + + private boolean isSameName(Dex oldDex, Dex newDex, int oldStringId, int newStringId) { + if (oldStringId == TableOfContents.Section.UNDEF_INDEX + && newStringId == TableOfContents.Section.UNDEF_INDEX) { + return true; + } + if (oldStringId == TableOfContents.Section.UNDEF_INDEX + || newStringId == TableOfContents.Section.UNDEF_INDEX) { + return false; + } + + return oldDex.strings().get(oldStringId).equals(newDex.strings().get(newStringId)); + } + + private boolean isSameAnnotationDirectory( + Dex oldDex, + Dex newDex, + int oldAnnotationDirectoryOffset, + int newAnnotationDirectoryOffset + ) { + if (oldAnnotationDirectoryOffset == 0 && newAnnotationDirectoryOffset == 0) { + return true; + } + + if (oldAnnotationDirectoryOffset == 0 || newAnnotationDirectoryOffset == 0) { + return false; + } + + AnnotationsDirectory oldAnnotationsDirectory = + oldDex.openSection(oldAnnotationDirectoryOffset).readAnnotationsDirectory(); + AnnotationsDirectory newAnnotationsDirectory = + newDex.openSection(newAnnotationDirectoryOffset).readAnnotationsDirectory(); + + if (!isSameAnnotationSet( + oldDex, + newDex, + oldAnnotationsDirectory.classAnnotationsOffset, + newAnnotationsDirectory.classAnnotationsOffset + )) { + return false; + } + + int[][] oldFieldAnnotations = oldAnnotationsDirectory.fieldAnnotations; + int[][] newFieldAnnotations = newAnnotationsDirectory.fieldAnnotations; + if (oldFieldAnnotations.length != newFieldAnnotations.length) { + return false; + } + for (int i = 0; i < oldFieldAnnotations.length; ++i) { + if (!isSameFieldId( + oldDex, newDex, oldFieldAnnotations[i][0], newFieldAnnotations[i][0] + )) { + return false; + } + if (!isSameAnnotationSet( + oldDex, newDex, oldFieldAnnotations[i][1], newFieldAnnotations[i][1] + )) { + return false; + } + } + + int[][] oldMethodAnnotations = oldAnnotationsDirectory.methodAnnotations; + int[][] newMethodAnnotations = newAnnotationsDirectory.methodAnnotations; + if (oldMethodAnnotations.length != newMethodAnnotations.length) { + return false; + } + for (int i = 0; i < oldMethodAnnotations.length; ++i) { + if (!isSameMethodId( + oldDex, newDex, oldMethodAnnotations[i][0], newMethodAnnotations[i][0] + )) { + return false; + } + if (!isSameAnnotationSet( + oldDex, newDex, oldMethodAnnotations[i][1], newMethodAnnotations[i][1] + )) { + return false; + } + } + + int[][] oldParameterAnnotations = oldAnnotationsDirectory.parameterAnnotations; + int[][] newParameterAnnotations = newAnnotationsDirectory.parameterAnnotations; + if (oldParameterAnnotations.length != newParameterAnnotations.length) { + return false; + } + for (int i = 0; i < oldParameterAnnotations.length; ++i) { + if (!isSameMethodId( + oldDex, newDex, oldParameterAnnotations[i][0], newParameterAnnotations[i][0] + )) { + return false; + } + if (!isSameAnnotationSetRefList( + oldDex, newDex, oldParameterAnnotations[i][1], newParameterAnnotations[i][1] + )) { + return false; + } + } + + return true; + } + + private boolean isSameFieldId(Dex oldDex, Dex newDex, int oldFieldIdIdx, int newFieldIdIdx) { + FieldId oldFieldId = oldDex.fieldIds().get(oldFieldIdIdx); + FieldId newFieldId = newDex.fieldIds().get(newFieldIdIdx); + + if (!isSameClassDesc( + oldDex, newDex, oldFieldId.declaringClassIndex, newFieldId.declaringClassIndex + )) { + return false; + } + + if (!isSameClassDesc( + oldDex, newDex, oldFieldId.typeIndex, newFieldId.typeIndex + )) { + return false; + } + + String oldName = oldDex.strings().get(oldFieldId.nameIndex); + String newName = newDex.strings().get(newFieldId.nameIndex); + return oldName.equals(newName); + } + + private boolean isSameMethodId(Dex oldDex, Dex newDex, int oldMethodIdIdx, int newMethodIdIdx) { + MethodId oldMethodId = oldDex.methodIds().get(oldMethodIdIdx); + MethodId newMethodId = newDex.methodIds().get(newMethodIdIdx); + + if (!isSameClassDesc( + oldDex, newDex, oldMethodId.declaringClassIndex, newMethodId.declaringClassIndex + )) { + return false; + } + + if (!isSameProtoId(oldDex, newDex, oldMethodId.protoIndex, newMethodId.protoIndex)) { + return false; + } + + String oldName = oldDex.strings().get(oldMethodId.nameIndex); + String newName = newDex.strings().get(newMethodId.nameIndex); + return oldName.equals(newName); + } + + private boolean isSameProtoId(Dex oldDex, Dex newDex, int oldProtoIdIdx, int newProtoIdIdx) { + ProtoId oldProtoId = oldDex.protoIds().get(oldProtoIdIdx); + ProtoId newProtoId = newDex.protoIds().get(newProtoIdIdx); + + String oldShorty = oldDex.strings().get(oldProtoId.shortyIndex); + String newShorty = newDex.strings().get(newProtoId.shortyIndex); + + if (!oldShorty.equals(newShorty)) { + return false; + } + + if (!isSameClassDesc( + oldDex, newDex, oldProtoId.returnTypeIndex, newProtoId.returnTypeIndex + )) { + return false; + } + + return isSameParameters( + oldDex, newDex, oldProtoId.parametersOffset, newProtoId.parametersOffset + ); + } + + private boolean isSameParameters( + Dex oldDex, Dex newDex, int oldParametersOffset, int newParametersOffset + ) { + if (oldParametersOffset == 0 && newParametersOffset == 0) { + return true; + } + + if (oldParametersOffset == 0 || newParametersOffset == 0) { + return false; + } + + TypeList oldParameters = oldDex.openSection(oldParametersOffset).readTypeList(); + TypeList newParameters = newDex.openSection(newParametersOffset).readTypeList(); + + if (oldParameters.types.length != newParameters.types.length) { + return false; + } + + for (int i = 0; i < oldParameters.types.length; ++i) { + if (!isSameClassDesc( + oldDex, newDex, oldParameters.types[i], newParameters.types[i] + )) { + return false; + } + } + + return true; + } + + private boolean isSameAnnotationSetRefList( + Dex oldDex, + Dex newDex, + int oldAnnotationSetRefListOffset, + int newAnnotationSetRefListOffset + ) { + if (oldAnnotationSetRefListOffset == 0 && newAnnotationSetRefListOffset == 0) { + return true; + } + + if (oldAnnotationSetRefListOffset == 0 || newAnnotationSetRefListOffset == 0) { + return false; + } + + AnnotationSetRefList oldAnnotationSetRefList = oldDex.openSection( + oldAnnotationSetRefListOffset + ).readAnnotationSetRefList(); + + AnnotationSetRefList newAnnotationSetRefList = newDex.openSection( + newAnnotationSetRefListOffset + ).readAnnotationSetRefList(); + + int oldAnnotationSetRefListCount = oldAnnotationSetRefList.annotationSetRefItems.length; + int newAnnotationSetRefListCount = newAnnotationSetRefList.annotationSetRefItems.length; + if (oldAnnotationSetRefListCount != newAnnotationSetRefListCount) { + return false; + } + + for (int i = 0; i < oldAnnotationSetRefListCount; ++i) { + if (!isSameAnnotationSet( + oldDex, + newDex, + oldAnnotationSetRefList.annotationSetRefItems[i], + newAnnotationSetRefList.annotationSetRefItems[i] + )) { + return false; + } + } + + return true; + } + + private boolean isSameAnnotationSet( + Dex oldDex, Dex newDex, int oldAnnotationSetOffset, int newAnnotationSetOffset + ) { + if (oldAnnotationSetOffset == 0 && newAnnotationSetOffset == 0) { + return true; + } + + if (oldAnnotationSetOffset == 0 || newAnnotationSetOffset == 0) { + return false; + } + + AnnotationSet oldClassAnnotationSet = + oldDex.openSection(oldAnnotationSetOffset).readAnnotationSet(); + AnnotationSet newClassAnnotationSet = + newDex.openSection(newAnnotationSetOffset).readAnnotationSet(); + + int oldAnnotationOffsetCount = oldClassAnnotationSet.annotationOffsets.length; + int newAnnotationOffsetCount = newClassAnnotationSet.annotationOffsets.length; + if (oldAnnotationOffsetCount != newAnnotationOffsetCount) { + return false; + } + + for (int i = 0; i < oldAnnotationOffsetCount; ++i) { + if (!isSameAnnotation( + oldDex, + newDex, + oldClassAnnotationSet.annotationOffsets[i], + newClassAnnotationSet.annotationOffsets[i] + )) { + return false; + } + } + + return true; + } + + private boolean isSameAnnotation( + Dex oldDex, Dex newDex, int oldAnnotationOffset, int newAnnotationOffset + ) { + Annotation oldAnnotation = oldDex.openSection(oldAnnotationOffset).readAnnotation(); + Annotation newAnnotation = newDex.openSection(newAnnotationOffset).readAnnotation(); + + if (oldAnnotation.visibility != newAnnotation.visibility) { + return false; + } + + EncodedValueReader oldAnnoReader = oldAnnotation.getReader(); + EncodedValueReader newAnnoReader = newAnnotation.getReader(); + + return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader); + } + + private boolean isSameAnnotationByReader( + Dex oldDex, + Dex newDex, + EncodedValueReader oldAnnoReader, + EncodedValueReader newAnnoReader + ) { + int oldFieldCount = oldAnnoReader.readAnnotation(); + int newFieldCount = newAnnoReader.readAnnotation(); + if (oldFieldCount != newFieldCount) { + return false; + } + + int oldAnnoType = oldAnnoReader.getAnnotationType(); + int newAnnoType = newAnnoReader.getAnnotationType(); + if (!isSameClassDesc(oldDex, newDex, oldAnnoType, newAnnoType)) { + return false; + } + + for (int i = 0; i < oldFieldCount; ++i) { + int oldAnnoNameIdx = oldAnnoReader.readAnnotationName(); + int newAnnoNameIdx = newAnnoReader.readAnnotationName(); + if (!isSameName(oldDex, newDex, oldAnnoNameIdx, newAnnoNameIdx)) { + return false; + } + if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) { + return false; + } + } + + return true; + } + + private boolean isSameEncodedValue( + Dex oldDex, + Dex newDex, + EncodedValueReader oldAnnoReader, + EncodedValueReader newAnnoReader + ) { + int oldAnnoItemType = oldAnnoReader.peek(); + int newAnnoItemType = newAnnoReader.peek(); + + if (oldAnnoItemType != newAnnoItemType) { + return false; + } + + switch (oldAnnoItemType) { + case EncodedValueReader.ENCODED_BYTE: { + byte oldByte = oldAnnoReader.readByte(); + byte newByte = newAnnoReader.readByte(); + return oldByte == newByte; + } + case EncodedValueReader.ENCODED_SHORT: { + short oldShort = oldAnnoReader.readShort(); + short newShort = newAnnoReader.readShort(); + return oldShort == newShort; + } + case EncodedValueReader.ENCODED_INT: { + int oldInt = oldAnnoReader.readInt(); + int newInt = newAnnoReader.readInt(); + return oldInt == newInt; + } + case EncodedValueReader.ENCODED_LONG: { + long oldLong = oldAnnoReader.readLong(); + long newLong = newAnnoReader.readLong(); + return oldLong == newLong; + } + case EncodedValueReader.ENCODED_CHAR: { + char oldChar = oldAnnoReader.readChar(); + char newChar = newAnnoReader.readChar(); + return oldChar == newChar; + } + case EncodedValueReader.ENCODED_FLOAT: { + float oldFloat = oldAnnoReader.readFloat(); + float newFloat = newAnnoReader.readFloat(); + return oldFloat == newFloat; + } + case EncodedValueReader.ENCODED_DOUBLE: { + double oldDouble = oldAnnoReader.readDouble(); + double newDouble = newAnnoReader.readDouble(); + return oldDouble == newDouble; + } + case EncodedValueReader.ENCODED_STRING: { + int oldStringIdx = oldAnnoReader.readString(); + int newStringIdx = newAnnoReader.readString(); + return isSameName(oldDex, newDex, oldStringIdx, newStringIdx); + } + case EncodedValueReader.ENCODED_TYPE: { + int oldTypeId = oldAnnoReader.readType(); + int newTypeId = newAnnoReader.readType(); + return isSameClassDesc(oldDex, newDex, oldTypeId, newTypeId); + } + case EncodedValueReader.ENCODED_FIELD: { + int oldFieldId = oldAnnoReader.readField(); + int newFieldId = newAnnoReader.readField(); + return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId); + } + case EncodedValueReader.ENCODED_ENUM: { + int oldFieldId = oldAnnoReader.readEnum(); + int newFieldId = newAnnoReader.readEnum(); + return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId); + } + case EncodedValueReader.ENCODED_METHOD: { + int oldMethodId = oldAnnoReader.readMethod(); + int newMethodId = newAnnoReader.readMethod(); + return isSameMethodId(oldDex, newDex, oldMethodId, newMethodId); + } + case EncodedValueReader.ENCODED_ARRAY: { + int oldArrSize = oldAnnoReader.readArray(); + int newArrSize = newAnnoReader.readArray(); + if (oldArrSize != newArrSize) { + return false; + } + for (int i = 0; i < oldArrSize; ++i) { + if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) { + return false; + } + } + return true; + } + case EncodedValueReader.ENCODED_ANNOTATION: { + return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader); + } + case EncodedValueReader.ENCODED_NULL: { + oldAnnoReader.readNull(); + newAnnoReader.readNull(); + return true; + } + case EncodedValueReader.ENCODED_BOOLEAN: { + boolean oldBool = oldAnnoReader.readBoolean(); + boolean newBool = newAnnoReader.readBoolean(); + return oldBool == newBool; + } + default: { + throw new IllegalStateException( + "Unexpected annotation value type: " + Integer.toHexString(oldAnnoItemType) + ); + } + } + } + + private boolean isSameClassData( + Dex oldDex, Dex newDex, int oldClassDataOffset, int newClassDataOffset + ) { + if (oldClassDataOffset == 0 && newClassDataOffset == 0) { + return true; + } + + if (oldClassDataOffset == 0 || newClassDataOffset == 0) { + return false; + } + + ClassData oldClassData = oldDex.openSection(oldClassDataOffset).readClassData(); + ClassData newClassData = newDex.openSection(newClassDataOffset).readClassData(); + + ClassData.Field[] oldInstanceFields = oldClassData.instanceFields; + ClassData.Field[] newInstanceFields = newClassData.instanceFields; + if (oldInstanceFields.length != newInstanceFields.length) { + return false; + } + for (int i = 0; i < oldInstanceFields.length; ++i) { + if (!isSameField(oldDex, newDex, oldInstanceFields[i], newInstanceFields[i])) { + return false; + } + } + + ClassData.Field[] oldStaticFields = oldClassData.staticFields; + ClassData.Field[] newStaticFields = newClassData.staticFields; + if (oldStaticFields.length != newStaticFields.length) { + return false; + } + for (int i = 0; i < oldStaticFields.length; ++i) { + if (!isSameField(oldDex, newDex, oldStaticFields[i], newStaticFields[i])) { + return false; + } + } + + ClassData.Method[] oldDirectMethods = oldClassData.directMethods; + ClassData.Method[] newDirectMethods = newClassData.directMethods; + if (oldDirectMethods.length != newDirectMethods.length) { + return false; + } + for (int i = 0; i < oldDirectMethods.length; ++i) { + if (!isSameMethod(oldDex, newDex, oldDirectMethods[i], newDirectMethods[i])) { + return false; + } + } + + ClassData.Method[] oldVirtualMethods = oldClassData.virtualMethods; + ClassData.Method[] newVirtualMethods = newClassData.virtualMethods; + if (oldVirtualMethods.length != newVirtualMethods.length) { + return false; + } + for (int i = 0; i < oldVirtualMethods.length; ++i) { + if (!isSameMethod(oldDex, newDex, oldVirtualMethods[i], newVirtualMethods[i])) { + return false; + } + } + + return true; + } + + private boolean isSameField( + Dex oldDex, Dex newDex, ClassData.Field oldField, ClassData.Field newField + ) { + if (oldField.accessFlags != newField.accessFlags) { + return false; + } + return isSameFieldId(oldDex, newDex, oldField.fieldIndex, newField.fieldIndex); + } + + private boolean isSameMethod( + Dex oldDex, Dex newDex, ClassData.Method oldMethod, ClassData.Method newMethod + ) { + if (oldMethod.accessFlags != newMethod.accessFlags) { + return false; + } + + if (!isSameMethodId(oldDex, newDex, oldMethod.methodIndex, newMethod.methodIndex)) { + return false; + } + + return isSameCode(oldDex, newDex, oldMethod.codeOffset, newMethod.codeOffset); + } + + private boolean isSameCode( + final Dex oldDex, final Dex newDex, int oldCodeOffset, int newCodeOffset + ) { + if (oldCodeOffset == 0 && newCodeOffset == 0) { + return true; + } + + if (oldCodeOffset == 0 || newCodeOffset == 0) { + return false; + } + + Code oldCode = oldDex.openSection(oldCodeOffset).readCode(); + Code newCode = newDex.openSection(newCodeOffset).readCode(); + + if (oldCode.registersSize != newCode.registersSize) { + return false; + } + + if (oldCode.insSize != newCode.insSize) { + return false; + } + + final InstructionComparator insnComparator = new InstructionComparator( + oldCode.instructions, + newCode.instructions + ) { + @Override + protected boolean compareString(int stringIndex1, int stringIndex2) { + return isSameName(oldDex, newDex, stringIndex1, stringIndex2); + } + + @Override + protected boolean compareType(int typeIndex1, int typeIndex2) { + return isSameClassDesc(oldDex, newDex, typeIndex1, typeIndex2); + } + + @Override + protected boolean compareField(int fieldIndex1, int fieldIndex2) { + return isSameFieldId(oldDex, newDex, fieldIndex1, fieldIndex2); + } + + @Override + protected boolean compareMethod(int methodIndex1, int methodIndex2) { + return isSameMethodId(oldDex, newDex, methodIndex1, methodIndex2); + } + }; + + if (!insnComparator.compare()) { + return false; + } + + if (!isSameDebugInfo( + oldDex, newDex, oldCode.debugInfoOffset, newCode.debugInfoOffset, insnComparator + )) { + return false; + } + + if (!isSameTries(oldDex, newDex, oldCode.tries, newCode.tries, insnComparator)) { + return false; + } + + return isSameCatchHandlers( + oldDex, newDex, oldCode.catchHandlers, newCode.catchHandlers, insnComparator + ); + } + + private boolean isSameDebugInfo( + Dex oldDex, + Dex newDex, + int oldDebugInfoOffset, + int newDebugInfoOffset, + InstructionComparator insnComparator + ) { + if (oldDebugInfoOffset == 0 && newDebugInfoOffset == 0) { + return true; + } + + if (oldDebugInfoOffset == 0 || newDebugInfoOffset == 0) { + return false; + } + + DebugInfoItem oldDebugInfoItem = + oldDex.openSection(oldDebugInfoOffset).readDebugInfoItem(); + DebugInfoItem newDebugInfoItem = + newDex.openSection(newDebugInfoOffset).readDebugInfoItem(); + + if (oldDebugInfoItem.lineStart != newDebugInfoItem.lineStart) { + return false; + } + + if (oldDebugInfoItem.parameterNames.length != newDebugInfoItem.parameterNames.length) { + return false; + } + + for (int i = 0; i < oldDebugInfoItem.parameterNames.length; ++i) { + int oldNameIdx = oldDebugInfoItem.parameterNames[i]; + int newNameIdx = newDebugInfoItem.parameterNames[i]; + if (!isSameName(oldDex, newDex, oldNameIdx, newNameIdx)) { + return false; + } + } + + DexDataBuffer oldDbgInfoBuffer = + new DexDataBuffer(ByteBuffer.wrap(oldDebugInfoItem.infoSTM)); + DexDataBuffer newDbgInfoBuffer = + new DexDataBuffer(ByteBuffer.wrap(newDebugInfoItem.infoSTM)); + + int oldLine = oldDebugInfoItem.lineStart; + int oldAddress = 0; + + int newLine = newDebugInfoItem.lineStart; + int newAddress = 0; + + while (oldDbgInfoBuffer.available() > 0 && newDbgInfoBuffer.available() > 0) { + int oldOpCode = oldDbgInfoBuffer.readUnsignedByte(); + int newOpCode = newDbgInfoBuffer.readUnsignedByte(); + + if (oldOpCode != newOpCode) { + if (oldOpCode < DBG_FIRST_SPECIAL || newOpCode < DBG_FIRST_SPECIAL) { + return false; + } + } + + int currOpCode = oldOpCode; + + switch (currOpCode) { + case DebugInfoItem.DBG_END_SEQUENCE: { + break; + } + case DebugInfoItem.DBG_ADVANCE_PC: { + int oldAddrDiff = oldDbgInfoBuffer.readUleb128(); + int newAddrDiff = newDbgInfoBuffer.readUleb128(); + oldAddress += oldAddrDiff; + newAddress += newAddrDiff; + if (!insnComparator.isSameInstruction(oldAddress, newAddress)) { + return false; + } + break; + } + case DebugInfoItem.DBG_ADVANCE_LINE: { + int oldLineDiff = oldDbgInfoBuffer.readSleb128(); + int newLineDiff = newDbgInfoBuffer.readSleb128(); + oldLine += oldLineDiff; + newLine += newLineDiff; + if (oldLine != newLine) { + return false; + } + break; + } + case DebugInfoItem.DBG_START_LOCAL: + case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { + int oldRegisterNum = oldDbgInfoBuffer.readUleb128(); + int newRegisterNum = newDbgInfoBuffer.readUleb128(); + if (oldRegisterNum != newRegisterNum) { + return false; + } + + int oldNameIndex = oldDbgInfoBuffer.readUleb128p1(); + int newNameIndex = newDbgInfoBuffer.readUleb128p1(); + if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) { + return false; + } + + int oldTypeIndex = oldDbgInfoBuffer.readUleb128p1(); + int newTypeIndex = newDbgInfoBuffer.readUleb128p1(); + if (!isSameClassDesc(oldDex, newDex, oldTypeIndex, newTypeIndex)) { + return false; + } + + if (currOpCode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { + int oldSigIndex = oldDbgInfoBuffer.readUleb128p1(); + int newSigIndex = newDbgInfoBuffer.readUleb128p1(); + if (!isSameName(oldDex, newDex, oldSigIndex, newSigIndex)) { + return false; + } + } + break; + } + case DebugInfoItem.DBG_END_LOCAL: + case DebugInfoItem.DBG_RESTART_LOCAL: { + int oldRegisterNum = oldDbgInfoBuffer.readUleb128(); + int newRegisterNum = newDbgInfoBuffer.readUleb128(); + if (oldRegisterNum != newRegisterNum) { + return false; + } + + break; + } + case DebugInfoItem.DBG_SET_FILE: { + int oldNameIndex = oldDbgInfoBuffer.readUleb128p1(); + int newNameIndex = newDbgInfoBuffer.readUleb128p1(); + if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) { + return false; + } + + break; + } + case DebugInfoItem.DBG_SET_PROLOGUE_END: + case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: { + break; + } + default: { + int oldAdjustedOpcode = oldOpCode - DBG_FIRST_SPECIAL; + oldLine += DBG_LINE_BASE + (oldAdjustedOpcode % DBG_LINE_RANGE); + oldAddress += (oldAdjustedOpcode / DBG_LINE_RANGE); + + int newAdjustedOpcode = newOpCode - DBG_FIRST_SPECIAL; + newLine += DBG_LINE_BASE + (newAdjustedOpcode % DBG_LINE_RANGE); + newAddress += (newAdjustedOpcode / DBG_LINE_RANGE); + + if (oldLine != newLine) { + return false; + } + if (!insnComparator.isSameInstruction(oldAddress, newAddress)) { + return false; + } + break; + } + } + } + + if (oldDbgInfoBuffer.available() > 0 || newDbgInfoBuffer.available() > 0) { + return false; + } + + return true; + } + + private boolean isSameTries( + Dex oldDex, + Dex newDex, + Code.Try[] oldTries, + Code.Try[] newTries, + InstructionComparator insnComparator + ) { + if (oldTries.length != newTries.length) { + return false; + } + + for (int i = 0; i < oldTries.length; ++i) { + Code.Try oldTry = oldTries[i]; + Code.Try newTry = newTries[i]; + if (oldTry.instructionCount != newTry.instructionCount) { + return false; + } + if (oldTry.catchHandlerIndex != newTry.catchHandlerIndex) { + return false; + } + if (!insnComparator.isSameInstruction(oldTry.startAddress, newTry.startAddress)) { + return false; + } + } + + return true; + } + + private boolean isSameCatchHandlers( + Dex oldDex, + Dex newDex, + Code.CatchHandler[] oldCatchHandlers, + Code.CatchHandler[] newCatchHandlers, + InstructionComparator insnComparator + ) { + if (oldCatchHandlers.length != newCatchHandlers.length) { + return false; + } + + for (int i = 0; i < oldCatchHandlers.length; ++i) { + Code.CatchHandler oldCatchHandler = oldCatchHandlers[i]; + Code.CatchHandler newCatchHandler = newCatchHandlers[i]; + + int oldTypeAddrPairCount = oldCatchHandler.typeIndexes.length; + int newTypeAddrPairCount = newCatchHandler.typeIndexes.length; + if (oldTypeAddrPairCount != newTypeAddrPairCount) { + return false; + } + + if (oldCatchHandler.catchAllAddress != -1 && newCatchHandler.catchAllAddress != -1) { + return insnComparator.isSameInstruction( + oldCatchHandler.catchAllAddress, newCatchHandler.catchAllAddress + ); + } else { + if (!(oldCatchHandler.catchAllAddress == -1 && newCatchHandler.catchAllAddress == -1)) { + return false; + } + } + + for (int j = 0; j < oldTypeAddrPairCount; ++j) { + if (!isSameClassDesc( + oldDex, + newDex, + oldCatchHandler.typeIndexes[j], + newCatchHandler.typeIndexes[j] + )) { + return false; + } + + if (!insnComparator.isSameInstruction( + oldCatchHandler.addresses[j], newCatchHandler.addresses[j] + )) { + return false; + } + } + } + + return true; + } + + public static final class DexClassInfo { + public String classDesc = null; + public int classDefIndex = ClassDef.NO_INDEX; + public ClassDef classDef = null; + public Dex owner = null; + + private DexClassInfo(String classDesc, int classDefIndex, ClassDef classDef, Dex owner) { + this.classDesc = classDesc; + this.classDef = classDef; + this.classDefIndex = classDefIndex; + this.owner = owner; + } + + private DexClassInfo() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return classDesc; + } + + @Override + public boolean equals(Object obj) { + DexClassInfo other = (DexClassInfo) obj; + if (!classDesc.equals(other.classDesc)) { + return false; + } + return owner.computeSignature(false).equals(other.owner.computeSignature(false)); + } + } + + public static final class DexGroup { + public final Dex[] dexes; + + private DexGroup(Dex... dexes) { + if (dexes == null || dexes.length == 0) { + throw new IllegalArgumentException("dexes is null or empty."); + } + this.dexes = new Dex[dexes.length]; + System.arraycopy(dexes, 0, this.dexes, 0, dexes.length); + } + + private DexGroup(File... dexFiles) throws IOException { + if (dexFiles == null || dexFiles.length == 0) { + throw new IllegalArgumentException("dexFiles is null or empty."); + } + this.dexes = new Dex[dexFiles.length]; + for (int i = 0; i < dexFiles.length; ++i) { + this.dexes[i] = new Dex(dexFiles[i]); + } + } + + private DexGroup(List dexFileList) throws IOException { + if (dexFileList == null || dexFileList.isEmpty()) { + throw new IllegalArgumentException("dexFileList is null or empty."); + } + this.dexes = new Dex[dexFileList.size()]; + for (int i = 0; i < this.dexes.length; ++i) { + this.dexes[i] = new Dex(dexFileList.get(i)); + } + } + + private DexGroup() { + throw new UnsupportedOperationException(); + } + + public static DexGroup wrap(Dex... dexes) { + return new DexGroup(dexes); + } + + public static DexGroup wrap(File... dexFiles) throws IOException { + return new DexGroup(dexFiles); + } + + public static DexGroup wrap(List dexFileList) throws IOException { + return new DexGroup(dexFileList); + } + + public Set getClassInfosInDexesWithDuplicateCheck() { + Map classDescToInfoMap = new HashMap<>(); + for (Dex dex : dexes) { + int classDefIndex = 0; + for (ClassDef classDef : dex.classDefs()) { + String classDesc = dex.typeNames().get(classDef.typeIndex); + if (!classDescToInfoMap.containsKey(classDesc)) { + classDescToInfoMap.put(classDesc, new DexClassInfo(classDesc, classDefIndex, classDef, dex)); + ++classDefIndex; + } else { + throw new IllegalStateException( + String.format( + "duplicate class descriptor [%s] in different dexes.", classDesc + ) + ); + } + } + } + return new HashSet<>(classDescToInfoMap.values()); + } + } +} + diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/ExcludedClassModifiedChecker.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/ExcludedClassModifiedChecker.java new file mode 100644 index 00000000..e87132ff --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/ExcludedClassModifiedChecker.java @@ -0,0 +1,209 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.DexFormat; +import com.tencent.tinker.build.dexpatcher.util.PatternUtils; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Created by tangyinsheng on 2016/4/14. + */ +public final class ExcludedClassModifiedChecker { + private static final int STMCODE_START = 0x00; + private static final int STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING = 0x01; + private static final int STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING = 0x02; + private static final int STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX = 0x03; + private static final int STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH = 0x04; + private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX = 0x05; + private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX = 0x06; + private static final int STMCODE_ERROR_LOADER_CLASS_CHANGED = 0x07; + private static final int STMCODE_END = 0x08; + private final Configuration config; + private final DexClassesComparator dexCmptor; + private Dex oldDex = null; + private Dex newDex = null; + private List deletedClassInfos = null; + private List addedClassInfos = null; + private Map changedClassInfosMap = null; + private Set oldClassesDescToCheck = new HashSet<>(); + private Set newClassesDescToCheck = new HashSet<>(); + + public ExcludedClassModifiedChecker(Configuration config) { + this.config = config; + this.dexCmptor = new DexClassesComparator(config.mDexLoaderPattern); + } + + public void checkIfExcludedClassWasModifiedInNewDex(File oldFile, File newFile) throws IOException, TinkerPatchException { + if (oldFile == null && newFile == null) { + throw new TinkerPatchException("both oldFile and newFile are null."); + } + + oldDex = (oldFile != null ? new Dex(oldFile) : null); + newDex = (newFile != null ? new Dex(newFile) : null); + + int stmCode = STMCODE_START; + + while (stmCode != STMCODE_END) { + switch (stmCode) { + /** + * Check rule: + * Loader classes must only appear in primary dex and each of them in primary old dex should keep + * completely consistent in new primary dex. + * + * An error is announced when any of these conditions below is fit: + * 1. Primary old dex is missing. + * 2. Primary new dex is missing. + * 3. There are not any loader classes in primary old dex. + * 4. There are some new loader classes added in new primary dex. + * 5. Loader classes in old primary dex are modified, deleted in new primary dex. + * 6. Loader classes are found in secondary old dexes. + * 7. Loader classes are found in secondary new dexes. + */ + case STMCODE_START: { + boolean isPrimaryDex = isPrimaryDex((oldFile == null ? newFile : oldFile)); + + if (isPrimaryDex) { + if (oldFile == null) { + stmCode = STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING; + } else if (newFile == null) { + stmCode = STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING; + } else { + dexCmptor.startCheck(oldDex, newDex); + deletedClassInfos = dexCmptor.getDeletedClassInfos(); + addedClassInfos = dexCmptor.getAddedClassInfos(); + changedClassInfosMap = dexCmptor.getChangedClassDescToInfosMap(); + + // All loader classes are in new dex, while none of them in old one. + if (deletedClassInfos.isEmpty() && changedClassInfosMap.isEmpty() && !addedClassInfos.isEmpty()) { + stmCode = STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX; + } else { + if (deletedClassInfos.isEmpty() && addedClassInfos.isEmpty()) { + // class descriptor is completely matches, see if any contents changes. + if (changedClassInfosMap.isEmpty()) { + stmCode = STMCODE_END; + } else { + stmCode = STMCODE_ERROR_LOADER_CLASS_CHANGED; + } + } else { + stmCode = STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH; + } + } + } + } else { + Set patternsOfClassDescToCheck = new HashSet<>(); + for (String patternStr : config.mDexLoaderPattern) { + patternsOfClassDescToCheck.add( + Pattern.compile( + PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) + ) + ); + } + + if (oldDex != null) { + oldClassesDescToCheck.clear(); + for (ClassDef classDef : oldDex.classDefs()) { + String desc = oldDex.typeNames().get(classDef.typeIndex); + if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { + oldClassesDescToCheck.add(desc); + } + } + if (!oldClassesDescToCheck.isEmpty()) { + stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX; + break; + } + } + + if (newDex != null) { + newClassesDescToCheck.clear(); + for (ClassDef classDef : newDex.classDefs()) { + String desc = newDex.typeNames().get(classDef.typeIndex); + if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { + newClassesDescToCheck.add(desc); + } + } + if (!newClassesDescToCheck.isEmpty()) { + stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX; + break; + } + } + + stmCode = STMCODE_END; + } + break; + } + case STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING: { + throw new TinkerPatchException("old primary dex is missing."); + } + case STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING: { + throw new TinkerPatchException("new primary dex is missing."); + } + case STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX: { + throw new TinkerPatchException("all loader classes don't appear in old primary dex."); + } + case STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH: { + throw new TinkerPatchException( + "loader classes in old primary dex are mismatched to those in new primary dex, \n" + + "if deleted classes is not empty, check if your dex division strategy is fine. \n" + + "added classes: " + Utils.collectionToString(addedClassInfos) + "\n" + + "deleted classes: " + Utils.collectionToString(deletedClassInfos) + ); + } + case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX: { + throw new TinkerPatchException("loader classes are found in old secondary dex. Found classes: " + Utils.collectionToString(oldClassesDescToCheck)); + } + case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX: { + throw new TinkerPatchException("loader classes are found in new secondary dex. Found classes: " + Utils.collectionToString(newClassesDescToCheck)); + } + case STMCODE_ERROR_LOADER_CLASS_CHANGED: { + String msg = + "some loader class has been changed in new dex." + + " Such these changes will not take effect!!" + + " related classes: " + + Utils.collectionToString(changedClassInfosMap.keySet()); + throw new TinkerPatchException(msg); + } + default: { + Logger.e("internal-error: unexpected stmCode."); + stmCode = STMCODE_END; + break; + } + } + } + } + + public boolean isPrimaryDex(File dexFile) { + Path dexFilePath = dexFile.toPath(); + Path parentPath = config.mTempUnzipOldDir.toPath(); + if (!dexFilePath.startsWith(parentPath)) { + parentPath = config.mTempUnzipNewDir.toPath(); + } + return DexFormat.DEX_IN_JAR_NAME.equals(parentPath.relativize(dexFilePath).toString().replace('\\', '/')); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/FileOperation.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/FileOperation.java new file mode 100644 index 00000000..62fa8630 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/FileOperation.java @@ -0,0 +1,401 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import com.tencent.tinker.build.patch.Configuration; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class FileOperation { + public static final boolean fileExists(String filePath) { + if (filePath == null) { + return false; + } + + File file = new File(filePath); + if (file.exists()) { + return true; + } + return false; + } + + public static final boolean deleteFile(String filePath) { + if (filePath == null) { + return true; + } + + File file = new File(filePath); + if (file.exists()) { + return file.delete(); + } + return true; + } + + public static final boolean deleteFile(File file) { + if (file == null) { + return true; + } + if (file.exists()) { + return file.delete(); + } + return true; + } + + public static boolean isLegalFile(String path) { + if (path == null) { + return false; + } + File file = new File(path); + return file.exists() && file.isFile() && file.length() > 0; + } + + public static long getFileSizes(File f) { + if (f == null) { + return 0; + } + long size = 0; + if (f.exists() && f.isFile()) { + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + size = fis.available(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (fis != null) { + fis.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return size; + } + + public static final boolean deleteDir(File file) { + if (file == null || (!file.exists())) { + return false; + } + if (file.isFile()) { + file.delete(); + } else if (file.isDirectory()) { + File[] files = file.listFiles(); + for (int i = 0; i < files.length; i++) { + deleteDir(files[i]); + } + } + file.delete(); + return true; + } + + public static void cleanDir(File dir) { + if (dir.exists()) { + FileOperation.deleteDir(dir); + dir.mkdirs(); + } + } + + public static void copyResourceUsingStream(String name, File dest) throws IOException { + FileOutputStream os = null; + File parent = dest.getParentFile(); + if (parent != null && (!parent.exists())) { + parent.mkdirs(); + } + InputStream is = null; + + try { + is = FileOperation.class.getResourceAsStream("/" + name); + os = new FileOutputStream(dest, false); + + byte[] buffer = new byte[TypedValue.BUFFER_SIZE]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + if (is != null) { + is.close(); + } + if (os != null) { + os.close(); + } + } + } + + public static void copyFileUsingStream(File source, File dest) throws IOException { + FileInputStream is = null; + FileOutputStream os = null; + File parent = dest.getParentFile(); + if (parent != null && (!parent.exists())) { + parent.mkdirs(); + } + try { + is = new FileInputStream(source); + os = new FileOutputStream(dest, false); + + byte[] buffer = new byte[TypedValue.BUFFER_SIZE]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + if (is != null) { + is.close(); + } + if (os != null) { + os.close(); + } + } + } + + public static boolean checkDirectory(String dir) { + File dirObj = new File(dir); + deleteDir(dirObj); + + if (!dirObj.exists()) { + dirObj.mkdirs(); + } + return true; + } + + @SuppressWarnings("rawtypes") + public static void unZipAPk(String fileName, String filePath) throws IOException { + checkDirectory(filePath); + + ZipFile zipFile = new ZipFile(fileName); + Enumeration enumeration = zipFile.entries(); + try { + while (enumeration.hasMoreElements()) { + ZipEntry entry = (ZipEntry) enumeration.nextElement(); + if (entry.isDirectory()) { + new File(filePath, entry.getName()).mkdirs(); + continue; + } + BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); + + File file = new File(filePath + File.separator + entry.getName()); + + File parentFile = file.getParentFile(); + if (parentFile != null && (!parentFile.exists())) { + parentFile.mkdirs(); + } + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos, TypedValue.BUFFER_SIZE); + + byte[] buf = new byte[TypedValue.BUFFER_SIZE]; + int len; + while ((len = bis.read(buf, 0, TypedValue.BUFFER_SIZE)) != -1) { + fos.write(buf, 0, len); + } + } finally { + if (bos != null) { + bos.flush(); + bos.close(); + } + if (bis != null) { + bis.close(); + } + } + } + } finally { + if (zipFile != null) { + zipFile.close(); + } + } + } + + /** + * zip list of file + * + * @param resFileList file(dir) list + * @param zipFile output zip file + * @throws IOException + */ + public static void zipFiles(Collection resFileList, File zipFile) throws IOException { + ZipOutputStream zipout = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile), TypedValue.BUFFER_SIZE)); + for (File resFile : resFileList) { + if (resFile.exists()) { + zipFile(resFile, zipout, ""); + } + } + zipout.close(); + } + + private static void zipFile(File resFile, ZipOutputStream zipout, String rootpath) throws IOException { + rootpath = rootpath + (rootpath.trim().length() == 0 ? "" : File.separator) + resFile.getName(); + if (resFile.isDirectory()) { + File[] fileList = resFile.listFiles(); + for (File file : fileList) { + zipFile(file, zipout, rootpath); + } + } else { + final byte[] fileContents = readContents(resFile); + //linux format!! + if (rootpath.contains("\\")) { + rootpath = rootpath.replace("\\", "/"); + } + ZipEntry entry = new ZipEntry(rootpath); +// if (compressMethod == ZipEntry.DEFLATED) { + entry.setMethod(ZipEntry.DEFLATED); +// } else { +// entry.setMethod(ZipEntry.STORED); +// entry.setSize(fileContents.length); +// final CRC32 checksumCalculator = new CRC32(); +// checksumCalculator.update(fileContents); +// entry.setCrc(checksumCalculator.getValue()); +// } + zipout.putNextEntry(entry); + zipout.write(fileContents); + zipout.flush(); + zipout.closeEntry(); + } + } + + private static byte[] readContents(final File file) throws IOException { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + final int bufferSize = TypedValue.BUFFER_SIZE; + try { + final FileInputStream in = new FileInputStream(file); + final BufferedInputStream bIn = new BufferedInputStream(in); + int length; + byte[] buffer = new byte[bufferSize]; + byte[] bufferCopy; + while ((length = bIn.read(buffer, 0, bufferSize)) != -1) { + bufferCopy = new byte[length]; + System.arraycopy(buffer, 0, bufferCopy, 0, length); + output.write(bufferCopy); + } + bIn.close(); + } finally { + output.close(); + } + return output.toByteArray(); + } + + public static long getFileCrc32(File file) throws IOException { + InputStream inputStream = new FileInputStream(file); + CRC32 crc = new CRC32(); + int cnt; + while ((cnt = inputStream.read()) != -1) { + crc.update(cnt); + } + return crc.getValue(); + } + + public static String getZipEntryCrc(File file, String entryName) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + ZipEntry entry = zipFile.getEntry(entryName); + if (entry == null) { + return null; + } + return String.valueOf(entry.getCrc()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return null; + } + + public static String getZipEntryMd5(File file, String entryName) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(file); + ZipEntry entry = zipFile.getEntry(entryName); + if (entry == null) { + return null; + } + return MD5.getMD5(zipFile.getInputStream(entry), 1024 * 100); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (zipFile != null) { + try { + zipFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return null; + } + + public static void zipInputDir(File inputDir, File outputFile) throws IOException { + File[] unzipFiles = inputDir.listFiles(); + List collectFiles = new ArrayList<>(); + for (File f : unzipFiles) { + collectFiles.add(f); + } + + FileOperation.zipFiles(collectFiles, outputFile); + } + + public static boolean sevenZipInputDir(File inputDir, File outputFile, Configuration config) { + String outPath = inputDir.getAbsolutePath(); + String path = outPath + File.separator + "*"; + String cmd = config.mSevenZipPath; + + ProcessBuilder pb = new ProcessBuilder(cmd, "a", "-tzip", outputFile.getAbsolutePath(), path, "-mx9"); + Process pro; + try { + pro = pb.start(); + InputStreamReader ir = new InputStreamReader(pro.getInputStream()); + LineNumberReader input = new LineNumberReader(ir); + while (input.readLine() != null) { + } + //destroy the stream + pro.waitFor(); + pro.destroy(); + } catch (IOException | InterruptedException e) { +// e.printStackTrace(); + FileOperation.deleteFile(outputFile); + Logger.e("7a patch file failed, you should set the zipArtifact, or set the path directly"); + return false; + } + return true; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Logger.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Logger.java new file mode 100644 index 00000000..965b87c8 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Logger.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import com.tencent.tinker.build.info.InfoWriter; +import com.tencent.tinker.build.patch.Configuration; + +import java.io.File; +import java.io.IOException; + +/** + * Created by zhangshaowen on 16/4/7. + */ +public class Logger { + private static InfoWriter logWriter; + + public static void initLogger(Configuration config) throws IOException { + String logPath = config.mOutFolder + File.separator + TypedValue.FILE_LOG; + logWriter = new InfoWriter(config, logPath); + } + + public static void closeLogger() { + logWriter.close(); + } + + public static void d(final String msg) { + Logger.d(msg, new Object[]{}); + } + + public static void d(final String format, final Object... obj) { + + String log = obj.length == 0 ? format : String.format(format, obj); + if (log == null) { + log = ""; + } + //add \n + System.out.printf(log + "\n"); + + logWriter.writeLineToInfoFile(log); + } + + public static void e(final String msg) { + Logger.e(msg, new Object[]{}); + } + + public static void e(final String format, final Object... obj) { + String log = obj.length == 0 ? format : String.format(format, obj); + if (log == null) { + log = ""; + } + //add \n + System.err.printf(log + "\n"); + logWriter.writeLineToInfoFile(log); + + } + +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/MD5.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/MD5.java new file mode 100644 index 00000000..a7106ec6 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/MD5.java @@ -0,0 +1,299 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; + +/** + *
MD5 digest wrapper
+ *
MD5计算封装
+ * + * @author zhaoyuan + */ +public final class MD5 { + + private MD5() { + + } + + /** + * get md5 string for input buffer + * + * @param buffer data to be calculated + * @return md5 result in string format + */ + public static String getMessageDigest(byte[] buffer) { + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(buffer); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + for (int i = 0; i < j; i++) { + byte byte0 = md[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + return null; + } + } + + /** + * get md5 in byte array + * + * @param buffer data to be calculated + * @return md5 result in byte array format + */ + public static byte[] getRawDigest(byte[] buffer) { + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(buffer); + return mdTemp.digest(); + + } catch (Exception e) { + return null; + } + } + + /** + * Get the md5 for inputStream. + * This method cost less memory. It read bufLen bytes from the FileInputStream once. + * + * @param is + * @param bufLen bytes number read from the stream once. + * The less bufLen is the more times getMD5() method takes. Also the less bufLen is the less memory cost. + */ + public static String getMD5(final InputStream is, final int bufLen, final int offset, final int length) { + return getMD5ExtendBytes(is, bufLen, offset, length, null); + } + + /** + * Get the md5 for inputStream. + * This method cost less memory. It read bufLen bytes from the FileInputStream once. + * + * @param is + * @param bufLen + * @param offset + * @param length + * @param extendBytes extend bytes which would be add to the end of input stream for MD5 calculating + */ + public static String getMD5ExtendBytes(final InputStream is, final int bufLen, final int offset, final int length, final byte[] extendBytes) { + if (is == null || bufLen <= 0 || offset < 0 || length <= 0) { + return null; + } + try { + long skipLen = is.skip(offset); + if (skipLen < offset) { // reach the end + return null; + } + + MessageDigest md = MessageDigest.getInstance("MD5"); + StringBuilder md5Str = new StringBuilder(32); + + byte[] buf = new byte[bufLen]; + int readCount = 0; + int totalRead = 0; + while ((readCount = is.read(buf)) != -1 && totalRead < length) { + if (totalRead + readCount <= length) { + md.update(buf, 0, readCount); + totalRead += readCount; + + } else { + md.update(buf, 0, length - totalRead); + totalRead = length; + } + } + if (extendBytes != null && extendBytes.length > 0) { + md.update(extendBytes); + } + + byte[] hashValue = md.digest(); + + for (int i = 0; i < hashValue.length; i++) { + md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); + } + return md5Str.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Get the md5 for inputStream. + * This method cost less memory. It read bufLen bytes from the FileInputStream once. + * + * @param is + * @param bufLen bytes number read from the stream once. + * The less bufLen is the more times getMD5() method takes. Also the less bufLen is the less memory cost. + */ + public static String getMD5(final InputStream is, final int bufLen) { + if (is == null || bufLen <= 0) { + return null; + } + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + StringBuilder md5Str = new StringBuilder(32); + + byte[] buf = new byte[bufLen]; + int readCount = 0; + while ((readCount = is.read(buf)) != -1) { + md.update(buf, 0, readCount); + } + + byte[] hashValue = md.digest(); + + for (int i = 0; i < hashValue.length; i++) { + md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); + } + return md5Str.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Get the md5 for the file, using less memory. + */ + public static String getMD5(final String file) { + if (file == null) { + return null; + } + + File f = new File(file); + if (f.exists()) { + return getMD5(f, 1024 * 100); + } + return null; + } + + /** + * Get the md5 for the file, using less memory. + */ + public static String getMD5(final File file) { + return getMD5(file, 1024 * 100); + } + + /** + * Get the md5 for the file. call getMD5(FileInputStream is, int bufLen) inside. + * + * @param file + * @param bufLen bytes number read from the stream once. + * The less bufLen is the more times getMD5() method takes. Also the less bufLen cost less memory. + */ + public static String getMD5(final File file, final int bufLen) { + if (file == null || bufLen <= 0 || !file.exists()) { + return null; + } + + FileInputStream fin = null; + try { + fin = new FileInputStream(file); + String md5 = MD5.getMD5(fin, (int) (bufLen <= file.length() ? bufLen : file.length())); + fin.close(); + return md5; + + } catch (Exception e) { + return null; + + } finally { + try { + if (fin != null) { + fin.close(); + } + } catch (IOException e) { + + } + } + } + + /** + * Get the md5 for the file, using less memory. + */ + public static String getMD5(final String file, final int offset, final int length) { + if (file == null) { + return null; + } + + File f = new File(file); + if (f.exists()) { + return getMD5(f, offset, length); + } + return null; + } + + /** + * Get the md5 for the file, using less memory. + */ + public static String getMD5(final File file, final int offset, final int length) { + if (file == null || !file.exists() || offset < 0 || length <= 0) { + return null; + } + + FileInputStream fin = null; + try { + fin = new FileInputStream(file); + String md5 = MD5.getMD5(fin, 1024 * 100, offset, length); + fin.close(); + return md5; + + } catch (Exception e) { + return null; + + } finally { + try { + if (fin != null) { + fin.close(); + } + } catch (IOException e) { + + } + } + } + + public static String getMD5ExtendBytes(final File file, final int offset, final int length, byte[] extend) { + if (file == null || !file.exists() || offset < 0 || length <= 0) { + return null; + } + + FileInputStream fin = null; + try { + fin = new FileInputStream(file); + String md5 = MD5.getMD5ExtendBytes(fin, 1024 * 100, offset, length, extend); + fin.close(); + return md5; + + } catch (Exception e) { + return null; + + } finally { + try { + if (fin != null) { + fin.close(); + } + } catch (IOException e) { + } + } + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TinkerPatchException.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TinkerPatchException.java new file mode 100644 index 00000000..6fb6ca26 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TinkerPatchException.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + + +/** + * @author zhangshaowen + */ +public class TinkerPatchException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public TinkerPatchException() { + } + + public TinkerPatchException(String message) { + super(message); + } + + public TinkerPatchException(String message, Throwable cause) { + super(message, cause); + } + + public TinkerPatchException(Throwable cause) { + super(cause); + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TypedValue.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TypedValue.java new file mode 100644 index 00000000..8d815700 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TypedValue.java @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +/** + * Container for a dynamically typed data value. Primarily used with + */ +public class TypedValue { + public static final int BUFFER_SIZE = 16384; + + public static final int K_BYTES = 1024; + + public static final String FILE_TXT = ".txt"; + public static final String FILE_XML = ".xml"; + public static final String FILE_APK = ".apk"; + public static final String FILE_CONFIG = "config.xml"; + public static final String FILE_LOG = "log.txt"; + public static final String SO_LOG_FILE = "so_log.txt"; + public static final String SO_META_FILE = "so_meta.txt"; + public static final String DEX_LOG_FILE = "dex_log.txt"; + public static final String DEX_META_FILE = "dex_meta.txt"; + public static final String DEX_TEMP_PATCH_DIR = "tempPatchedDexes"; + public static final String DEX_SMALLPATCH_INFO_FILE = "smallpatch_info.ddextra"; + public static final String RES_LOG_FILE = "res_log.txt"; + public static final String RES_META_TXT = "res_meta.txt"; + + public static final String FILE_ASSETS = "assets"; + + public static final String TINKER_ID = "TINKER_ID"; + public static final String NEW_TINKER_ID = "NEW_TINKER_ID"; + + public static final String PACKAGE_META_FILE = "package_meta.txt"; + + public static final String PATH_DEFAULT_OUTPUT = "tinkerPatch"; + + public static final String PATH_PATCH_FILES = "tinker_result"; + public static final String OUT_7ZIP_FILE_PATH = "out_7zip"; + + public static final int ANDROID_40_API_LEVEL = 14; + public static final double DEX_PATCH_MAX_RATIO = 0.6; + public static final double DEX_JAR_PATCH_MAX_RATIO = 1.0; + public static final double BSDIFF_PATCH_MAX_RATIO = 0.8; + + public static final String RES_ARSC = "resources.arsc"; + public static final String RES_MANIFEST = "AndroidManifest.xml"; + public static final String RES_OUT = "resources_out.zip"; + public static final String RES_OUT_7ZIP = "resources_out_7z.zip"; + public static final String RES_OUT_MD5_TAG = "%RES_OUT_MD5_TAG%"; + + public static final int ADD = 1; + public static final int MOD = 2; + public static final int DEL = 3; + public static final int LARGE_MOD = 4; + + public static final String ADD_TITLE = "add:"; + public static final String MOD_TITLE = "modify:"; + public static final String LARGE_MOD_TITLE = "large modify:"; + public static final String DEL_TITLE = "delete:"; + public static final String PATTERN_TITLE = "pattern:"; +} diff --git a/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Utils.java b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Utils.java new file mode 100644 index 00000000..bcc20917 --- /dev/null +++ b/tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Utils.java @@ -0,0 +1,213 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.build.util; + +import com.tencent.tinker.build.decoder.ResDiffDecoder; +import com.tencent.tinker.build.patch.Configuration; +import com.tencent.tinker.commons.resutil.ResUtil; +import com.tencent.tinker.commons.ziputil.TinkerZipEntry; +import com.tencent.tinker.commons.ziputil.TinkerZipFile; +import com.tencent.tinker.commons.ziputil.TinkerZipOutputStream; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.regex.Pattern; + +/** + * Created by sun on 1/9/16. + */ +public class Utils { + public static boolean isPresent(String str) { + return str != null && str.length() > 0; + } + + public static boolean isBlank(String str) { + return !isPresent(str); + } + + public static boolean isPresent(Iterator iterator) { + return iterator != null && iterator.hasNext(); + } + + public static boolean isBlank(Iterator iterator) { + return !isPresent(iterator); + } + + public static String convertToPatternString(String input) { + //convert \\. + if (input.contains(".")) { + input = input.replaceAll("\\.", "\\\\."); + } + //convert ?to . + if (input.contains("?")) { + input = input.replaceAll("\\?", "\\."); + } + //convert * to.* + if (input.contains("*")) { + input = input.replace("*", ".*"); + } + return input; + } + + public static boolean isStringMatchesPatterns(String str, Collection patterns) { + for (Pattern pattern : patterns) { + if (pattern.matcher(str).matches()) { + return true; + } + } + return false; + } + + public static String collectionToString(Collection collection) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + boolean isFirstElement = true; + for (T element : collection) { + if (isFirstElement) { + isFirstElement = false; + } else { + sb.append(','); + } + sb.append(element); + } + sb.append('}'); + return sb.toString(); + } + + public static boolean checkFileInPattern(HashSet patterns, String key) { + if (!patterns.isEmpty()) { + for (Iterator it = patterns.iterator(); it.hasNext();) { + Pattern p = it.next(); + if (p.matcher(key).matches()) { + return true; + } + } + } + return false; + } + + public static String genResOutputFile(File output, File newZipFile, Configuration config, + ArrayList addedSet, ArrayList modifiedSet, ArrayList deletedSet, + ArrayList largeModifiedSet, HashMap largeModifiedMap) throws IOException { + TinkerZipFile oldApk = new TinkerZipFile(config.mOldApkFile); + TinkerZipFile newApk = new TinkerZipFile(newZipFile); + TinkerZipOutputStream out = new TinkerZipOutputStream(new BufferedOutputStream(new FileOutputStream(output))); + + try { + final Enumeration entries = oldApk.entries(); + while (entries.hasMoreElements()) { + TinkerZipEntry zipEntry = entries.nextElement(); + if (zipEntry == null) { + throw new TinkerPatchException( + String.format("zipEntry is null when get from oldApk") + ); + } + String name = zipEntry.getName(); + if (Utils.checkFileInPattern(config.mResFilePattern, name)) { + //won't contain in add set. + if (!deletedSet.contains(name) + && !modifiedSet.contains(name) + && !largeModifiedSet.contains(name) + && !name.equals(TypedValue.RES_MANIFEST)) { + ResUtil.extractTinkerEntry(oldApk, zipEntry, out); + } + } + } + //process manifest + TinkerZipEntry manifestZipEntry = oldApk.getEntry(TypedValue.RES_MANIFEST); + if (manifestZipEntry == null) { + throw new TinkerPatchException( + String.format("can't found resource file %s from old apk file %s", TypedValue.RES_MANIFEST, config.mOldApkFile.getAbsolutePath()) + ); + } + ResUtil.extractTinkerEntry(oldApk, manifestZipEntry, out); + + for (String name : largeModifiedSet) { + TinkerZipEntry largeZipEntry = oldApk.getEntry(name); + if (largeZipEntry == null) { + throw new TinkerPatchException( + String.format("can't found resource file %s from old apk file %s", name, config.mOldApkFile.getAbsolutePath()) + ); + } + ResDiffDecoder.LargeModeInfo largeModeInfo = largeModifiedMap.get(name); + ResUtil.extractLargeModifyFile(largeZipEntry, largeModeInfo.path, largeModeInfo.crc, out); + } + + for (String name : addedSet) { + TinkerZipEntry addZipEntry = newApk.getEntry(name); + if (addZipEntry == null) { + throw new TinkerPatchException( + String.format("can't found add resource file %s from new apk file %s", name, config.mNewApkFile.getAbsolutePath()) + ); + } + ResUtil.extractTinkerEntry(newApk, addZipEntry, out); + } + + for (String name : modifiedSet) { + TinkerZipEntry modZipEntry = newApk.getEntry(name); + if (modZipEntry == null) { + throw new TinkerPatchException( + String.format("can't found add resource file %s from new apk file %s", name, config.mNewApkFile.getAbsolutePath()) + ); + } + ResUtil.extractTinkerEntry(newApk, modZipEntry, out); + } + } finally { + out.close(); + oldApk.close(); + newApk.close(); + } + return MD5.getMD5(output); + } + + public static String getResourceMeta(String baseCrc, String md5) { + return TypedValue.RES_OUT + "," + baseCrc + "," + md5; + } + + /** + * if bsDiff result is too larger, just treat it as newly file + * @param bsDiffFile + * @param newFile + * @return + */ + public static boolean checkBsDiffFileSize(File bsDiffFile, File newFile) { + if (!bsDiffFile.exists()) { + throw new TinkerPatchException("can not find the bsDiff file:" + bsDiffFile.getAbsolutePath()); + } + + //check bsDiffFile file size + double ratio = bsDiffFile.length() / (double) newFile.length(); + if (ratio > TypedValue.BSDIFF_PATCH_MAX_RATIO) { + Logger.e("bsDiff patch file:%s, size:%dk, new file:%s, size:%dk. patch file is too large, treat it as newly file to save patch time!", + bsDiffFile.getName(), + bsDiffFile.length() / 1024, + newFile.getName(), + newFile.length() / 1024 + ); + return false; + } + return true; + } +} diff --git a/tinker-build/tinker-patch-lib/src/main/resources/test.dex b/tinker-build/tinker-patch-lib/src/main/resources/test.dex new file mode 100644 index 00000000..e620dcc3 Binary files /dev/null and b/tinker-build/tinker-patch-lib/src/main/resources/test.dex differ diff --git a/tinker-commons/.gitignore b/tinker-commons/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tinker-commons/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tinker-commons/NOTICE.txt b/tinker-commons/NOTICE.txt new file mode 100644 index 00000000..a513af6c --- /dev/null +++ b/tinker-commons/NOTICE.txt @@ -0,0 +1,190 @@ + Original work Copyright (c) 2005-2008, The Android Open Source Project + Modified work Copyright (C) 2016 THL A29 Limited, a Tencent company. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/tinker-commons/build.gradle b/tinker-commons/build.gradle new file mode 100644 index 00000000..eb94594d --- /dev/null +++ b/tinker-commons/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'java' + +version rootProject.ext.VERSION_NAME +group rootProject.ext.GROUP + +[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':third-party:aosp-dexutils') + compile project(':third-party:bsdiff-util') +} + +task buildSdk(type: Copy, dependsOn: [build]) { + from('build/libs') { + include '*.jar' + exclude '*javadoc.jar' + exclude '*-sources.jar' + } + into(rootProject.file("buildSdk/android")) +} + +apply from: rootProject.file('gradle/java-artifacts.gradle') +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/tinker-commons/gradle.properties b/tinker-commons/gradle.properties new file mode 100644 index 00000000..2f2fe324 --- /dev/null +++ b/tinker-commons/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=tinker-commons +POM_NAME=Tinker Common libs +POM_PACKAGING=jar \ No newline at end of file diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatchApplier.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatchApplier.java new file mode 100644 index 00000000..0d37fdcb --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatchApplier.java @@ -0,0 +1,403 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.util.CompareUtils; +import com.tencent.tinker.android.dx.util.Hex; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.AnnotationSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.AnnotationSetRefListSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.AnnotationSetSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.AnnotationsDirectorySectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.ClassDataSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.ClassDefSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.CodeSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.DebugInfoItemSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.DexSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.FieldIdSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.MethodIdSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.ProtoIdSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.StaticValueSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.StringDataSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.TypeIdSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.algorithms.patch.TypeListSectionPatchAlgorithm; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * Created by tangyinsheng on 2016/6/30. + */ +public class DexPatchApplier { + private final Dex oldDex; + private final Dex patchedDex; + private final DexPatchFile patchFile; + private final SmallPatchedDexItemFile extraInfoFile; + private final IndexMap oldToFullPatchedIndexMap; + private final IndexMap patchedToSmallPatchedIndexMap; + + private final String oldDexSignStr; + + private DexSectionPatchAlgorithm stringDataSectionPatchAlg; + private DexSectionPatchAlgorithm typeIdSectionPatchAlg; + private DexSectionPatchAlgorithm protoIdSectionPatchAlg; + private DexSectionPatchAlgorithm fieldIdSectionPatchAlg; + private DexSectionPatchAlgorithm methodIdSectionPatchAlg; + private DexSectionPatchAlgorithm classDefSectionPatchAlg; + private DexSectionPatchAlgorithm typeListSectionPatchAlg; + private DexSectionPatchAlgorithm annotationSetRefListSectionPatchAlg; + private DexSectionPatchAlgorithm annotationSetSectionPatchAlg; + private DexSectionPatchAlgorithm classDataSectionPatchAlg; + private DexSectionPatchAlgorithm codeSectionPatchAlg; + private DexSectionPatchAlgorithm debugInfoSectionPatchAlg; + private DexSectionPatchAlgorithm annotationSectionPatchAlg; + private DexSectionPatchAlgorithm encodedArraySectionPatchAlg; + private DexSectionPatchAlgorithm annotationsDirectorySectionPatchAlg; + + public DexPatchApplier(File oldDexIn, File patchFileIn) throws IOException { + this( + new Dex(oldDexIn), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + null + ); + } + + public DexPatchApplier(InputStream oldDexIn, InputStream patchFileIn) throws IOException { + this( + new Dex(oldDexIn), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + null + ); + } + + public DexPatchApplier(InputStream oldDexIn, int initDexSize, InputStream patchFileIn) throws IOException { + this( + new Dex(oldDexIn, initDexSize), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + null + ); + } + + public DexPatchApplier( + File oldDexIn, + File patchFileIn, + SmallPatchedDexItemFile extraInfoFile + ) throws IOException { + this( + new Dex(oldDexIn), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + extraInfoFile + ); + } + + public DexPatchApplier( + InputStream oldDexIn, + InputStream patchFileIn, + SmallPatchedDexItemFile extraInfoFile + ) throws IOException { + this( + new Dex(oldDexIn), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + extraInfoFile + ); + } + + public DexPatchApplier( + InputStream oldDexIn, + int initDexSize, + InputStream patchFileIn, + SmallPatchedDexItemFile extraInfoFile + ) throws IOException { + this( + new Dex(oldDexIn, initDexSize), + (patchFileIn != null ? new DexPatchFile(patchFileIn) : null), + extraInfoFile + ); + } + + public DexPatchApplier( + Dex oldDexIn, + DexPatchFile patchFileIn, + SmallPatchedDexItemFile extraAddedDexElementsFile + ) { + this.oldDex = oldDexIn; + this.oldDexSignStr = Hex.toHexString(oldDexIn.computeSignature(false)); + this.patchFile = patchFileIn; + if (extraAddedDexElementsFile == null) { + this.patchedDex = new Dex(patchFileIn.getPatchedDexSize()); + } else { + this.patchedDex = new Dex( + extraAddedDexElementsFile.getPatchedDexSizeByOldDexSign(this.oldDexSignStr) + ); + } + this.oldToFullPatchedIndexMap = new IndexMap(); + this.patchedToSmallPatchedIndexMap = (extraAddedDexElementsFile != null ? new IndexMap() : null); + this.extraInfoFile = extraAddedDexElementsFile; + + if ((patchFileIn == null) && (extraAddedDexElementsFile == null + || extraAddedDexElementsFile.isAffectedOldDex(this.oldDexSignStr))) { + throw new UnsupportedOperationException( + "patchFileIn is null, and extraAddedDexElementFile" + + "(smallPatchInfo) is null or does not mention " + + "oldDexIn. Consider copy oldDexIn instead." + ); + } + } + + public void executeAndSaveTo(OutputStream out) throws IOException { + // Before executing, we should check if this patch can be applied to + // old dex we passed in. + byte[] oldDexSign = this.oldDex.computeSignature(false); + if (oldDexSign == null) { + throw new IOException("failed to compute old dex's signature."); + } + + byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature(); + if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) { + throw new IOException( + String.format( + "old dex signature mismatch! expected: %s, actual: %s", + Arrays.toString(oldDexSign), + Arrays.toString(oldDexSignInPatchFile) + ) + ); + } + + String oldDexSignStr = Hex.toHexString(oldDexSign); + + // Firstly, set sections' offset after patched, sort according to their offset so that + // the dex lib of aosp can calculate section size. + TableOfContents patchedToc = this.patchedDex.getTableOfContents(); + + patchedToc.header.off = 0; + patchedToc.header.size = 1; + patchedToc.mapList.size = 1; + + if (extraInfoFile == null || !extraInfoFile.isAffectedOldDex(this.oldDexSignStr)) { + patchedToc.stringIds.off + = this.patchFile.getPatchedStringIdSectionOffset(); + patchedToc.typeIds.off + = this.patchFile.getPatchedTypeIdSectionOffset(); + patchedToc.typeLists.off + = this.patchFile.getPatchedTypeListSectionOffset(); + patchedToc.protoIds.off + = this.patchFile.getPatchedProtoIdSectionOffset(); + patchedToc.fieldIds.off + = this.patchFile.getPatchedFieldIdSectionOffset(); + patchedToc.methodIds.off + = this.patchFile.getPatchedMethodIdSectionOffset(); + patchedToc.classDefs.off + = this.patchFile.getPatchedClassDefSectionOffset(); + patchedToc.mapList.off + = this.patchFile.getPatchedMapListSectionOffset(); + patchedToc.stringDatas.off + = this.patchFile.getPatchedStringDataSectionOffset(); + patchedToc.annotations.off + = this.patchFile.getPatchedAnnotationSectionOffset(); + patchedToc.annotationSets.off + = this.patchFile.getPatchedAnnotationSetSectionOffset(); + patchedToc.annotationSetRefLists.off + = this.patchFile.getPatchedAnnotationSetRefListSectionOffset(); + patchedToc.annotationsDirectories.off + = this.patchFile.getPatchedAnnotationsDirectorySectionOffset(); + patchedToc.encodedArrays.off + = this.patchFile.getPatchedEncodedArraySectionOffset(); + patchedToc.debugInfos.off + = this.patchFile.getPatchedDebugInfoSectionOffset(); + patchedToc.codes.off + = this.patchFile.getPatchedCodeSectionOffset(); + patchedToc.classDatas.off + = this.patchFile.getPatchedClassDataSectionOffset(); + patchedToc.fileSize + = this.patchFile.getPatchedDexSize(); + } else { + patchedToc.stringIds.off + = this.extraInfoFile.getPatchedStringIdOffsetByOldDexSign(oldDexSignStr); + patchedToc.typeIds.off + = this.extraInfoFile.getPatchedTypeIdOffsetByOldDexSign(oldDexSignStr); + patchedToc.typeLists.off + = this.extraInfoFile.getPatchedTypeListOffsetByOldDexSign(oldDexSignStr); + patchedToc.protoIds.off + = this.extraInfoFile.getPatchedProtoIdOffsetByOldDexSign(oldDexSignStr); + patchedToc.fieldIds.off + = this.extraInfoFile.getPatchedFieldIdOffsetByOldDexSign(oldDexSignStr); + patchedToc.methodIds.off + = this.extraInfoFile.getPatchedMethodIdOffsetByOldDexSign(oldDexSignStr); + patchedToc.classDefs.off + = this.extraInfoFile.getPatchedClassDefOffsetByOldDexSign(oldDexSignStr); + patchedToc.mapList.off + = this.extraInfoFile.getPatchedMapListOffsetByOldDexSign(oldDexSignStr); + patchedToc.stringDatas.off + = this.extraInfoFile.getPatchedStringDataOffsetByOldDexSign(oldDexSignStr); + patchedToc.annotations.off + = this.extraInfoFile.getPatchedAnnotationOffsetByOldDexSign(oldDexSignStr); + patchedToc.annotationSets.off + = this.extraInfoFile.getPatchedAnnotationSetOffsetByOldDexSign(oldDexSignStr); + patchedToc.annotationSetRefLists.off + = this.extraInfoFile.getPatchedAnnotationSetRefListOffsetByOldDexSign(oldDexSignStr); + patchedToc.annotationsDirectories.off + = this.extraInfoFile.getPatchedAnnotationsDirectoryOffsetByOldDexSign(oldDexSignStr); + patchedToc.encodedArrays.off + = this.extraInfoFile.getPatchedEncodedArrayOffsetByOldDexSign(oldDexSignStr); + patchedToc.debugInfos.off + = this.extraInfoFile.getPatchedDebugInfoOffsetByOldDexSign(oldDexSignStr); + patchedToc.codes.off + = this.extraInfoFile.getPatchedCodeOffsetByOldDexSign(oldDexSignStr); + patchedToc.classDatas.off + = this.extraInfoFile.getPatchedClassDataOffsetByOldDexSign(oldDexSignStr); + patchedToc.fileSize + = this.extraInfoFile.getPatchedDexSizeByOldDexSign(oldDexSignStr); + } + + Arrays.sort(patchedToc.sections); + + patchedToc.computeSizesFromOffsets(); + + // Secondly, run patch algorithms according to sections' dependencies. + this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm( + patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, + patchedToSmallPatchedIndexMap, extraInfoFile + ); + + this.stringDataSectionPatchAlg.execute(); + this.typeIdSectionPatchAlg.execute(); + this.typeListSectionPatchAlg.execute(); + this.protoIdSectionPatchAlg.execute(); + this.fieldIdSectionPatchAlg.execute(); + this.methodIdSectionPatchAlg.execute(); + Runtime.getRuntime().gc(); + this.annotationSectionPatchAlg.execute(); + this.annotationSetSectionPatchAlg.execute(); + this.annotationSetRefListSectionPatchAlg.execute(); + this.annotationsDirectorySectionPatchAlg.execute(); + Runtime.getRuntime().gc(); + this.debugInfoSectionPatchAlg.execute(); + this.codeSectionPatchAlg.execute(); + Runtime.getRuntime().gc(); + this.classDataSectionPatchAlg.execute(); + this.encodedArraySectionPatchAlg.execute(); + this.classDefSectionPatchAlg.execute(); + Runtime.getRuntime().gc(); + + // Thirdly, write header, mapList. Calculate and write patched dex's sign and checksum. + Dex.Section headerOut = this.patchedDex.openSection(patchedToc.header.off); + patchedToc.writeHeader(headerOut); + + Dex.Section mapListOut = this.patchedDex.openSection(patchedToc.mapList.off); + patchedToc.writeMap(mapListOut); + + this.patchedDex.writeHashes(); + + // Finally, write patched dex to file. + this.patchedDex.writeTo(out); + } + + public void executeAndSaveTo(File file) throws IOException { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + executeAndSaveTo(os); + } finally { + if (os != null) { + try { + os.close(); + } catch (Exception e) { + // ignored. + } + } + } + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatcherLogger.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatcherLogger.java new file mode 100644 index 00000000..3ee24910 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/DexPatcherLogger.java @@ -0,0 +1,61 @@ +package com.tencent.tinker.commons.dexpatcher; + +/** + * Created by tangyinsheng on 2016/9/18. + */ + +public final class DexPatcherLogger { + private IDexPatcherLogger loggerImpl = null; + + public void setLoggerImpl(IDexPatcherLogger dexPatcherLogger) { + this.loggerImpl = dexPatcherLogger; + } + + public void v(String tag, String fmt, Object... vals) { + if (this.loggerImpl != null) { + fmt = "[V][" + tag + "] " + fmt; + this.loggerImpl.v((vals == null || vals.length == 0) ? fmt : String.format(fmt, vals)); + } + } + + public void d(String tag, String fmt, Object... vals) { + if (this.loggerImpl != null) { + fmt = "[D][" + tag + "] " + fmt; + this.loggerImpl.d((vals == null || vals.length == 0) ? fmt : String.format(fmt, vals)); + } + } + + public void i(String tag, String fmt, Object... vals) { + if (this.loggerImpl != null) { + fmt = "[I][" + tag + "] " + fmt; + this.loggerImpl.i((vals == null || vals.length == 0) ? fmt : String.format(fmt, vals)); + } + } + + public void w(String tag, String fmt, Object... vals) { + if (this.loggerImpl != null) { + fmt = "[W][" + tag + "] " + fmt; + this.loggerImpl.w((vals == null || vals.length == 0) ? fmt : String.format(fmt, vals)); + } + } + + public void e(String tag, String fmt, Object... vals) { + if (this.loggerImpl != null) { + fmt = "[E][" + tag + "] " + fmt; + this.loggerImpl.e((vals == null || vals.length == 0) ? fmt : String.format(fmt, vals)); + } + } + + + public interface IDexPatcherLogger { + void v(String msg); + + void d(String msg); + + void i(String msg); + + void w(String msg); + + void e(String msg); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSectionPatchAlgorithm.java new file mode 100644 index 00000000..f6a32d6e --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Annotation; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class AnnotationSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedAnnotationTocSec = null; + private Dex.Section patchedAnnotationSec = null; + + public AnnotationSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isAnnotationInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public AnnotationSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedAnnotationTocSec = patchedDex.getTableOfContents().annotations; + this.patchedAnnotationSec = patchedDex.openSection(this.patchedAnnotationTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotations; + } + + @Override + protected Annotation nextItem(DexDataBuffer section) { + return section.readAnnotation(); + } + + @Override + protected int getItemSize(Annotation item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedAnnotationSectionOffset(); + } + + @Override + protected Annotation adjustItem(IndexMap indexMap, Annotation item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(Annotation patchedItem) { + ++this.patchedAnnotationTocSec.size; + return this.patchedAnnotationSec.writeAnnotation(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetRefListSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetRefListSectionPatchAlgorithm.java new file mode 100644 index 00000000..8b837f83 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetRefListSectionPatchAlgorithm.java @@ -0,0 +1,127 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.AnnotationSetRefList; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class AnnotationSetRefListSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedAnnotationSetRefListTocSec = null; + private Dex.Section patchedAnnotationSetRefListSec = null; + + public AnnotationSetRefListSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isAnnotationSetRefListInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public AnnotationSetRefListSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedAnnotationSetRefListTocSec + = patchedDex.getTableOfContents().annotationSetRefLists; + this.patchedAnnotationSetRefListSec + = patchedDex.openSection(this.patchedAnnotationSetRefListTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationSetRefLists; + } + + @Override + protected AnnotationSetRefList nextItem(DexDataBuffer section) { + return section.readAnnotationSetRefList(); + } + + @Override + protected int getItemSize(AnnotationSetRefList item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedAnnotationSetRefListSectionOffset(); + } + + @Override + protected AnnotationSetRefList adjustItem(IndexMap indexMap, AnnotationSetRefList item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(AnnotationSetRefList patchedItem) { + ++this.patchedAnnotationSetRefListTocSec.size; + return this.patchedAnnotationSetRefListSec.writeAnnotationSetRefList(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationSetRefListOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationSetRefListDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetSectionPatchAlgorithm.java new file mode 100644 index 00000000..acbd8086 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationSetSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.AnnotationSet; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class AnnotationSetSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedAnnotationSetTocSec = null; + private Dex.Section patchedAnnotationSetSec = null; + + public AnnotationSetSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isAnnotationSetInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public AnnotationSetSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedAnnotationSetTocSec = patchedDex.getTableOfContents().annotationSets; + this.patchedAnnotationSetSec = patchedDex.openSection(this.patchedAnnotationSetTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationSets; + } + + @Override + protected AnnotationSet nextItem(DexDataBuffer section) { + return section.readAnnotationSet(); + } + + @Override + protected int getItemSize(AnnotationSet item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedAnnotationSetSectionOffset(); + } + + @Override + protected AnnotationSet adjustItem(IndexMap indexMap, AnnotationSet item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(AnnotationSet patchedItem) { + ++this.patchedAnnotationSetTocSec.size; + return this.patchedAnnotationSetSec.writeAnnotationSet(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationSetOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationSetDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationsDirectorySectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationsDirectorySectionPatchAlgorithm.java new file mode 100644 index 00000000..d703ca7d --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/AnnotationsDirectorySectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.AnnotationsDirectory; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class AnnotationsDirectorySectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedAnnotationsDirectoryTocSec = null; + private Dex.Section patchedAnnotationsDirectorySec = null; + + public AnnotationsDirectorySectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isAnnotationsDirectoryInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public AnnotationsDirectorySectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedAnnotationsDirectoryTocSec = patchedDex.getTableOfContents().annotationsDirectories; + this.patchedAnnotationsDirectorySec = patchedDex.openSection(this.patchedAnnotationsDirectoryTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().annotationsDirectories; + } + + @Override + protected AnnotationsDirectory nextItem(DexDataBuffer section) { + return section.readAnnotationsDirectory(); + } + + @Override + protected int getItemSize(AnnotationsDirectory item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedAnnotationsDirectorySectionOffset(); + } + + @Override + protected AnnotationsDirectory adjustItem(IndexMap indexMap, AnnotationsDirectory item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(AnnotationsDirectory patchedItem) { + ++this.patchedAnnotationsDirectoryTocSec.size; + return this.patchedAnnotationsDirectorySec.writeAnnotationsDirectory(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapAnnotationsDirectoryOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markAnnotationsDirectoryDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDataSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDataSectionPatchAlgorithm.java new file mode 100644 index 00000000..0de65223 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDataSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.ClassData; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class ClassDataSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedClassDataTocSec = null; + private Dex.Section patchedClassDataSec = null; + + public ClassDataSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isClassDataInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public ClassDataSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedClassDataTocSec = patchedDex.getTableOfContents().classDatas; + this.patchedClassDataSec = patchedDex.openSection(this.patchedClassDataTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().classDatas; + } + + @Override + protected ClassData nextItem(DexDataBuffer section) { + return section.readClassData(); + } + + @Override + protected int getItemSize(ClassData item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedClassDataSectionOffset(); + } + + @Override + protected ClassData adjustItem(IndexMap indexMap, ClassData item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(ClassData patchedItem) { + ++this.patchedClassDataTocSec.size; + return this.patchedClassDataSec.writeClassData(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapClassDataOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markClassDataDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDefSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDefSectionPatchAlgorithm.java new file mode 100644 index 00000000..9000d65d --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ClassDefSectionPatchAlgorithm.java @@ -0,0 +1,114 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.ClassDef; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class ClassDefSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedClassDefTocSec = null; + private Dex.Section patchedClassDefSec = null; + + public ClassDefSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isClassDefInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public ClassDefSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedClassDefTocSec = patchedDex.getTableOfContents().classDefs; + this.patchedClassDefSec = patchedDex.openSection(this.patchedClassDefTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().classDefs; + } + + @Override + protected ClassDef nextItem(DexDataBuffer section) { + return section.readClassDef(); + } + + + @Override + protected int getItemSize(ClassDef item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedClassDefSectionOffset(); + } + + @Override + protected ClassDef adjustItem(IndexMap indexMap, ClassDef item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(ClassDef patchedItem) { + ++this.patchedClassDefTocSec.size; + return this.patchedClassDefSec.writeClassDef(patchedItem); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/CodeSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/CodeSectionPatchAlgorithm.java new file mode 100644 index 00000000..edea8a0f --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/CodeSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Code; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class CodeSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedCodeTocSec = null; + private Dex.Section patchedCodeSec = null; + + public CodeSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isCodeInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public CodeSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedCodeTocSec = patchedDex.getTableOfContents().codes; + this.patchedCodeSec = patchedDex.openSection(this.patchedCodeTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().codes; + } + + @Override + protected Code nextItem(DexDataBuffer section) { + return section.readCode(); + } + + @Override + protected int getItemSize(Code item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedCodeSectionOffset(); + } + + @Override + protected Code adjustItem(IndexMap indexMap, Code item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(Code patchedItem) { + ++this.patchedCodeTocSec.size; + return this.patchedCodeSec.writeCode(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapCodeOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markCodeDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DebugInfoItemSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DebugInfoItemSectionPatchAlgorithm.java new file mode 100644 index 00000000..000451ed --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DebugInfoItemSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.DebugInfoItem; +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class DebugInfoItemSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedDebugInfoItemTocSec = null; + private Dex.Section patchedDebugInfoItemSec = null; + + public DebugInfoItemSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isDebugInfoInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public DebugInfoItemSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedDebugInfoItemTocSec = patchedDex.getTableOfContents().debugInfos; + this.patchedDebugInfoItemSec = patchedDex.openSection(this.patchedDebugInfoItemTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().debugInfos; + } + + @Override + protected DebugInfoItem nextItem(DexDataBuffer section) { + return section.readDebugInfoItem(); + } + + @Override + protected int getItemSize(DebugInfoItem item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedDebugInfoSectionOffset(); + } + + @Override + protected DebugInfoItem adjustItem(IndexMap indexMap, DebugInfoItem item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(DebugInfoItem patchedItem) { + ++this.patchedDebugInfoItemTocSec.size; + return this.patchedDebugInfoItemSec.writeDebugInfoItem(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapDebugInfoItemOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markDebugInfoItemDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DexSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DexSectionPatchAlgorithm.java new file mode 100644 index 00000000..62b746f0 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/DexSectionPatchAlgorithm.java @@ -0,0 +1,436 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.Hex; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; + +import java.util.Arrays; + +/** + * Created by tangyinsheng on 2016/6/29. + */ +public abstract class DexSectionPatchAlgorithm> { + protected final DexPatchFile patchFile; + + protected final Dex oldDex; + + /** + * IndexMap for mapping old item to corresponding one in full patch. + */ + private final IndexMap oldToFullPatchedIndexMap; + + /** + * IndexMap for mapping item in full patch to corresponding one in small patch. + */ + private final IndexMap fullPatchedToSmallPatchedIndexMap; + + /** + * Signature string of dex we're processing. For extra info file usage. + */ + private final String oldDexSignStr; + private SmallPatchedDexItemChooser smallPatchedDexItemChooser = null; + + public DexSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap + ) { + this(patchFile, oldDex, oldToFullPatchedIndexMap, fullPatchedToSmallPatchedIndexMap, null); + } + + public DexSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser smallPatchedDexItemChooser + ) { + this.patchFile = patchFile; + this.oldDex = oldDex; + this.oldToFullPatchedIndexMap = oldToFullPatchedIndexMap; + this.fullPatchedToSmallPatchedIndexMap = fullPatchedToSmallPatchedIndexMap; + this.oldDexSignStr = Hex.toHexString(oldDex.computeSignature(false)); + this.smallPatchedDexItemChooser = smallPatchedDexItemChooser; + } + + /** + * Get {@code Section} in {@code TableOfContents}. + */ + protected abstract TableOfContents.Section getTocSection(Dex dex); + + /** + * Get next item in {@code section}. + */ + protected abstract T nextItem(DexDataBuffer section); + + /** + * Get size of {@code item}. + */ + protected abstract int getItemSize(T item); + + /** + * Adjust {@code item} using specific {@code indexMap} + */ + protected T adjustItem(IndexMap indexMap, T item) { + return item; + } + + /** + * Update index or offset mapping in {@code indexMap}. + */ + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + // Should override by subclass if needed. + } + + /** + * Mark deleted index or offset in {@code indexMap}. + */ + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + // Should override by subclass if needed. + } + + /** + * Judge if item on index {@code patchedIndex} should be kept in small dex. + */ + protected final boolean isPatchedItemInSmallPatchedDex(String oldDexSignStr, int patchedIndex) { + if (this.smallPatchedDexItemChooser != null) { + return this.smallPatchedDexItemChooser + .isPatchedItemInSmallPatchedDex(oldDexSignStr, patchedIndex); + } else { + return true; + } + } + + /** + * Return base offset of current section in full patched dex. + */ + protected abstract int getFullPatchSectionBase(); + + /** + * Output patched item. This method should be overrided by subclass + * so that patched item can be written to right place. + *

+ * Returns the offset where {@code patchedItem} is written. (Must be valid.) + */ + protected abstract int writePatchedItem(T patchedItem); + + private int[] readDeltaIndiciesOrOffsets(int count) { + int[] result = new int[count]; + int lastVal = 0; + for (int i = 0; i < count; ++i) { + int delta = patchFile.getBuffer().readSleb128(); + lastVal = lastVal + delta; + result[i] = lastVal; + } + return result; + } + + /** + * Adapter method for item's offset fetching, if an item is not + * inherited from {@code Item} (which means it is a simple item in dex section + * that doesn't need multiple members to describe), this method + * return {@code index} instead. + */ + private int getItemOffsetOrIndex(int index, T item) { + if (item instanceof TableOfContents.Section.Item) { + return ((TableOfContents.Section.Item) item).off; + } else { + return index; + } + } + + public void execute() { + int deletedItemCount; + int[] deletedIndices; + + int addedItemCount; + int[] addedIndices; + + int replacedItemCount; + int[] replacedIndices; + + if (patchFile != null) { + deletedItemCount = patchFile.getBuffer().readUleb128(); + deletedIndices = readDeltaIndiciesOrOffsets(deletedItemCount); + + addedItemCount = patchFile.getBuffer().readUleb128(); + addedIndices = readDeltaIndiciesOrOffsets(addedItemCount); + + replacedItemCount = patchFile.getBuffer().readUleb128(); + replacedIndices = readDeltaIndiciesOrOffsets(replacedItemCount); + } else { + deletedItemCount = 0; + deletedIndices = new int[deletedItemCount]; + + addedItemCount = 0; + addedIndices = new int[addedItemCount]; + + replacedItemCount = 0; + replacedIndices = new int[replacedItemCount]; + } + + TableOfContents.Section tocSec = getTocSection(this.oldDex); + Dex.Section oldSection = null; + + int oldItemCount = 0; + if (tocSec.exists()) { + oldSection = this.oldDex.openSection(tocSec); + oldItemCount = tocSec.size; + } + + // Now rest data are added and replaced items arranged in the order of + // added indices and replaced indices. + boolean genFullPatchDex = (fullPatchedToSmallPatchedIndexMap == null); + + if (genFullPatchDex) { + doFullPatch( + oldSection, oldItemCount, deletedIndices, addedIndices, replacedIndices + ); + } else { + doSmallPatch( + oldSection, oldItemCount, deletedIndices, addedIndices, replacedIndices + ); + } + } + + private void doFullPatch( + Dex.Section oldSection, + int oldItemCount, + int[] deletedIndices, + int[] addedIndices, + int[] replacedIndices + ) { + int deletedItemCount = deletedIndices.length; + int addedItemCount = addedIndices.length; + int replacedItemCount = replacedIndices.length; + int newItemCount = oldItemCount + addedItemCount - deletedItemCount; + + int deletedItemCounter = 0; + int addActionCursor = 0; + int replaceActionCursor = 0; + + int oldIndex = 0; + int patchedIndex = 0; + while (oldIndex < oldItemCount || patchedIndex < newItemCount) { + if (addActionCursor < addedItemCount && addedIndices[addActionCursor] == patchedIndex) { + T addedItem = nextItem(patchFile.getBuffer()); + int patchedOffset = writePatchedItem(addedItem); + ++addActionCursor; + ++patchedIndex; + } else + if (replaceActionCursor < replacedItemCount && replacedIndices[replaceActionCursor] == patchedIndex) { + T replacedItem = nextItem(patchFile.getBuffer()); + int patchedOffset = writePatchedItem(replacedItem); + ++replaceActionCursor; + ++patchedIndex; + } else + if (Arrays.binarySearch(deletedIndices, oldIndex) >= 0) { + T skippedOldItem = nextItem(oldSection); // skip old item. + ++oldIndex; + ++deletedItemCounter; + } else + if (Arrays.binarySearch(replacedIndices, oldIndex) >= 0) { + T skippedOldItem = nextItem(oldSection); // skip old item. + ++oldIndex; + } else + if (oldIndex < oldItemCount) { + T oldItem = adjustItem(this.oldToFullPatchedIndexMap, nextItem(oldSection)); + + int patchedOffset = writePatchedItem(oldItem); + + updateIndexOrOffset( + this.oldToFullPatchedIndexMap, + oldIndex, + getItemOffsetOrIndex(oldIndex, oldItem), + patchedIndex, + patchedOffset + ); + + ++oldIndex; + ++patchedIndex; + } + } + + if (addActionCursor != addedItemCount || deletedItemCounter != deletedItemCount + || replaceActionCursor != replacedItemCount + ) { + throw new IllegalStateException( + String.format( + "bad patch operation sequence. addCounter: %d, addCount: %d, " + + "delCounter: %d, delCount: %d, " + + "replaceCounter: %d, replaceCount:%d", + addActionCursor, + addedItemCount, + deletedItemCounter, + deletedItemCount, + replaceActionCursor, + replacedItemCount + ) + ); + } + } + + private void doSmallPatch( + Dex.Section oldSection, + int oldItemCount, + int[] deletedIndices, + int[] addedIndices, + int[] replacedIndices + ) { + int deletedItemCount = deletedIndices.length; + int addedItemCount = addedIndices.length; + int replacedItemCount = replacedIndices.length; + int newItemCount = oldItemCount + addedItemCount - deletedItemCount; + + int deletedItemCounter = 0; + int addActionCursor = 0; + int replaceActionCursor = 0; + + int oldIndex = 0; + int fullPatchedIndex = 0; + int fullPatchedOffset = getFullPatchSectionBase(); + int smallPatchedIndex = 0; + while (oldIndex < oldItemCount || fullPatchedIndex < newItemCount) { + if (addActionCursor < addedItemCount && addedIndices[addActionCursor] == fullPatchedIndex) { + T addedItem = nextItem(patchFile.getBuffer()); + ++addActionCursor; + + if (getTocSection(oldDex).isElementFourByteAligned) { + fullPatchedOffset = SizeOf.roundToTimesOfFour(fullPatchedOffset); + } + + if (isPatchedItemInSmallPatchedDex(this.oldDexSignStr, fullPatchedIndex)) { + T adjustedItem = adjustItem(fullPatchedToSmallPatchedIndexMap, addedItem); + int smallPatchedOffset = writePatchedItem(adjustedItem); + updateIndexOrOffset( + fullPatchedToSmallPatchedIndexMap, + fullPatchedIndex, + fullPatchedOffset, + smallPatchedIndex, + smallPatchedOffset + ); + ++smallPatchedIndex; + } + + ++fullPatchedIndex; + fullPatchedOffset += getItemSize(addedItem); + } else + if (replaceActionCursor < replacedItemCount && replacedIndices[replaceActionCursor] == fullPatchedIndex) { + T replacedItem = nextItem(patchFile.getBuffer()); + ++replaceActionCursor; + + if (getTocSection(oldDex).isElementFourByteAligned) { + fullPatchedOffset = SizeOf.roundToTimesOfFour(fullPatchedOffset); + } + + if (isPatchedItemInSmallPatchedDex(this.oldDexSignStr, fullPatchedIndex)) { + T adjustedItem = adjustItem(fullPatchedToSmallPatchedIndexMap, replacedItem); + int smallPatchedOffset = writePatchedItem(adjustedItem); + updateIndexOrOffset( + fullPatchedToSmallPatchedIndexMap, + fullPatchedIndex, + fullPatchedOffset, + smallPatchedIndex, + smallPatchedOffset + ); + ++smallPatchedIndex; + } + + ++fullPatchedIndex; + fullPatchedOffset += getItemSize(replacedItem); + } else + if (Arrays.binarySearch(deletedIndices, oldIndex) >= 0) { + T skippedOldItem = nextItem(oldSection); // skip old item. + ++oldIndex; + ++deletedItemCounter; + } else + if (Arrays.binarySearch(replacedIndices, oldIndex) >= 0) { + T skippedOldItem = nextItem(oldSection); // skip old item. + ++oldIndex; + } else + if (oldIndex < oldItemCount) { + T oldItem = nextItem(oldSection); + T oldItemInFullPatch = adjustItem(this.oldToFullPatchedIndexMap, oldItem); + + if (getTocSection(oldDex).isElementFourByteAligned) { + fullPatchedOffset = SizeOf.roundToTimesOfFour(fullPatchedOffset); + } + + if (isPatchedItemInSmallPatchedDex(this.oldDexSignStr, fullPatchedIndex)) { + T patchedItemInSmallPatch = adjustItem( + this.fullPatchedToSmallPatchedIndexMap, oldItemInFullPatch + ); + int smallPatchedOffset = writePatchedItem(patchedItemInSmallPatch); + updateIndexOrOffset( + fullPatchedToSmallPatchedIndexMap, + fullPatchedIndex, + fullPatchedOffset, + smallPatchedIndex, + smallPatchedOffset + ); + ++smallPatchedIndex; + } + + updateIndexOrOffset( + oldToFullPatchedIndexMap, + oldIndex, + getItemOffsetOrIndex(oldIndex, oldItem), + fullPatchedIndex, + fullPatchedOffset + ); + + ++fullPatchedIndex; + fullPatchedOffset += getItemSize(oldItemInFullPatch); + + ++oldIndex; + } + } + + if (addActionCursor != addedItemCount || deletedItemCounter != deletedItemCount + || replaceActionCursor != replacedItemCount + ) { + throw new IllegalStateException( + String.format( + "bad patch operation sequence. addCounter: %d, addCount: %d, " + + "delCounter: %d, delCount: %d, " + + "replaceCounter: %d, replaceCount:%d", + addActionCursor, + addedItemCount, + deletedItemCounter, + deletedItemCount, + replaceActionCursor, + replacedItemCount + ) + ); + } + } + + /** + * Indicates if an item in full patched dex with specific index + * should be kept in small patched dex of current old dex. + */ + public interface SmallPatchedDexItemChooser { + boolean isPatchedItemInSmallPatchedDex(String oldDexSign, int patchedItemIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/FieldIdSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/FieldIdSectionPatchAlgorithm.java new file mode 100644 index 00000000..16c9ece3 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/FieldIdSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.FieldId; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class FieldIdSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedFieldIdTocSec = null; + private Dex.Section patchedFieldIdSec = null; + + public FieldIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isFieldIdInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public FieldIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedFieldIdTocSec = patchedDex.getTableOfContents().fieldIds; + this.patchedFieldIdSec = patchedDex.openSection(this.patchedFieldIdTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().fieldIds; + } + + @Override + protected FieldId nextItem(DexDataBuffer section) { + return section.readFieldId(); + } + + @Override + protected int getItemSize(FieldId item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedFieldIdSectionOffset(); + } + + @Override + protected FieldId adjustItem(IndexMap indexMap, FieldId item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(FieldId patchedItem) { + ++this.patchedFieldIdTocSec.size; + return this.patchedFieldIdSec.writeFieldId(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapFieldIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markFieldIdDeleted(deletedIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/MethodIdSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/MethodIdSectionPatchAlgorithm.java new file mode 100644 index 00000000..ddff7cc9 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/MethodIdSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.MethodId; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class MethodIdSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedMethodIdTocSec = null; + private Dex.Section patchedMethodIdSec = null; + + public MethodIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isMethodIdInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public MethodIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedMethodIdTocSec = patchedDex.getTableOfContents().methodIds; + this.patchedMethodIdSec = patchedDex.openSection(this.patchedMethodIdTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().methodIds; + } + + @Override + protected MethodId nextItem(DexDataBuffer section) { + return section.readMethodId(); + } + + @Override + protected int getItemSize(MethodId item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedMethodIdSectionOffset(); + } + + @Override + protected MethodId adjustItem(IndexMap indexMap, MethodId item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(MethodId patchedItem) { + ++this.patchedMethodIdTocSec.size; + return this.patchedMethodIdSec.writeMethodId(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapMethodIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markMethodIdDeleted(deletedIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ProtoIdSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ProtoIdSectionPatchAlgorithm.java new file mode 100644 index 00000000..2e5e2d28 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/ProtoIdSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.ProtoId; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class ProtoIdSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedProtoIdTocSec = null; + private Dex.Section patchedProtoIdSec = null; + + public ProtoIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isProtoIdInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public ProtoIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedProtoIdTocSec = patchedDex.getTableOfContents().protoIds; + this.patchedProtoIdSec = patchedDex.openSection(this.patchedProtoIdTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().protoIds; + } + + @Override + protected ProtoId nextItem(DexDataBuffer section) { + return section.readProtoId(); + } + + @Override + protected int getItemSize(ProtoId item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedProtoIdSectionOffset(); + } + + @Override + protected ProtoId adjustItem(IndexMap indexMap, ProtoId item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(ProtoId patchedItem) { + ++this.patchedProtoIdTocSec.size; + return this.patchedProtoIdSec.writeProtoId(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapProtoIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markProtoIdDeleted(deletedIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StaticValueSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StaticValueSectionPatchAlgorithm.java new file mode 100644 index 00000000..6a25503b --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StaticValueSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.EncodedValue; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class StaticValueSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedEncodedValueTocSec = null; + private Dex.Section patchedEncodedValueSec = null; + + public StaticValueSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isEncodedArrayInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public StaticValueSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedEncodedValueTocSec = patchedDex.getTableOfContents().encodedArrays; + this.patchedEncodedValueSec = patchedDex.openSection(this.patchedEncodedValueTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().encodedArrays; + } + + @Override + protected EncodedValue nextItem(DexDataBuffer section) { + return section.readEncodedArray(); + } + + @Override + protected int getItemSize(EncodedValue item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedEncodedArraySectionOffset(); + } + + @Override + protected EncodedValue adjustItem(IndexMap indexMap, EncodedValue item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(EncodedValue patchedItem) { + ++this.patchedEncodedValueTocSec.size; + return this.patchedEncodedValueSec.writeEncodedArray(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapStaticValuesOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markStaticValuesDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StringDataSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StringDataSectionPatchAlgorithm.java new file mode 100644 index 00000000..44fc7411 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/StringDataSectionPatchAlgorithm.java @@ -0,0 +1,127 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.StringData; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class StringDataSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedStringDataTocSec = null; + private TableOfContents.Section patchedStringIdTocSec = null; + private Dex.Section patchedStringDataSec = null; + private Dex.Section patchedStringIdSec = null; + + public StringDataSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isStringInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public StringDataSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedStringDataTocSec = patchedDex.getTableOfContents().stringDatas; + this.patchedStringIdTocSec = patchedDex.getTableOfContents().stringIds; + this.patchedStringDataSec = patchedDex.openSection(this.patchedStringDataTocSec); + this.patchedStringIdSec = patchedDex.openSection(this.patchedStringIdTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().stringDatas; + } + + @Override + protected StringData nextItem(DexDataBuffer section) { + return section.readStringData(); + } + + @Override + protected int getItemSize(StringData item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedStringDataSectionOffset(); + } + + @Override + protected int writePatchedItem(StringData patchedItem) { + int off = this.patchedStringDataSec.writeStringData(patchedItem); + this.patchedStringIdSec.writeInt(off); + ++this.patchedStringDataTocSec.size; + ++this.patchedStringIdTocSec.size; + return off; + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapStringIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markStringIdDeleted(deletedIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeIdSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeIdSectionPatchAlgorithm.java new file mode 100644 index 00000000..5b590c2d --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeIdSectionPatchAlgorithm.java @@ -0,0 +1,127 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class TypeIdSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedTypeIdTocSec = null; + private Dex.Section patchedTypeIdSec = null; + + public TypeIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isTypeIdInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public TypeIdSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedTypeIdTocSec = patchedDex.getTableOfContents().typeIds; + this.patchedTypeIdSec = patchedDex.openSection(this.patchedTypeIdTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().typeIds; + } + + @Override + protected Integer nextItem(DexDataBuffer section) { + return section.readInt(); + } + + @Override + protected int getItemSize(Integer item) { + return SizeOf.UINT; + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedTypeIdSectionOffset(); + } + + @Override + protected Integer adjustItem(IndexMap indexMap, Integer item) { + return indexMap.adjustStringIndex(item); + } + + @Override + protected int writePatchedItem(Integer patchedItem) { + int off = this.patchedTypeIdSec.position(); + this.patchedTypeIdSec.writeInt(patchedItem); + ++this.patchedTypeIdTocSec.size; + return off; + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldIndex != newIndex) { + indexMap.mapTypeIds(oldIndex, newIndex); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markTypeIdDeleted(deletedIndex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeListSectionPatchAlgorithm.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeListSectionPatchAlgorithm.java new file mode 100644 index 00000000..8e3ad559 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/algorithms/patch/TypeListSectionPatchAlgorithm.java @@ -0,0 +1,125 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.algorithms.patch; + +import com.tencent.tinker.android.dex.Dex; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.TypeList; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dx.util.IndexMap; +import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; +import com.tencent.tinker.commons.dexpatcher.struct.SmallPatchedDexItemFile; + +/** + * Created by tangyinsheng on 2016/7/4. + */ +public class TypeListSectionPatchAlgorithm extends DexSectionPatchAlgorithm { + private TableOfContents.Section patchedTypeListTocSec = null; + private Dex.Section patchedTypeListSec = null; + + public TypeListSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + final SmallPatchedDexItemFile extraInfoFile + ) { + this( + patchFile, + oldDex, + patchedDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + new SmallPatchedDexItemChooser() { + @Override + public boolean isPatchedItemInSmallPatchedDex( + String oldDexSign, int patchedItemIndex + ) { + return extraInfoFile.isTypeListInSmallPatchedDex( + oldDexSign, patchedItemIndex + ); + } + } + ); + } + + public TypeListSectionPatchAlgorithm( + DexPatchFile patchFile, + Dex oldDex, + Dex patchedDex, + IndexMap oldToFullPatchedIndexMap, + IndexMap fullPatchedToSmallPatchedIndexMap, + SmallPatchedDexItemChooser spdItemChooser + ) { + super( + patchFile, + oldDex, + oldToFullPatchedIndexMap, + fullPatchedToSmallPatchedIndexMap, + spdItemChooser + ); + + if (patchedDex != null) { + this.patchedTypeListTocSec = patchedDex.getTableOfContents().typeLists; + this.patchedTypeListSec = patchedDex.openSection(this.patchedTypeListTocSec); + } + } + + @Override + protected TableOfContents.Section getTocSection(Dex dex) { + return dex.getTableOfContents().typeLists; + } + + @Override + protected TypeList nextItem(DexDataBuffer section) { + return section.readTypeList(); + } + + @Override + protected int getItemSize(TypeList item) { + return item.byteCountInDex(); + } + + @Override + protected int getFullPatchSectionBase() { + return this.patchFile.getPatchedTypeListSectionOffset(); + } + + @Override + protected TypeList adjustItem(IndexMap indexMap, TypeList item) { + return indexMap.adjust(item); + } + + @Override + protected int writePatchedItem(TypeList patchedItem) { + ++this.patchedTypeListTocSec.size; + return this.patchedTypeListSec.writeTypeList(patchedItem); + } + + @Override + protected void updateIndexOrOffset(IndexMap indexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { + if (oldOffset != newOffset) { + indexMap.mapTypeListOffset(oldOffset, newOffset); + } + } + + @Override + protected void markDeletedIndexOrOffset(IndexMap indexMap, int deletedIndex, int deletedOffset) { + indexMap.markTypeListDeleted(deletedOffset); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/DexPatchFile.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/DexPatchFile.java new file mode 100644 index 00000000..1cb356f4 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/DexPatchFile.java @@ -0,0 +1,302 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.struct; + +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.TableOfContents; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dex.util.CompareUtils; +import com.tencent.tinker.android.dex.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by tangyinsheng on 2016/7/1. + */ +public final class DexPatchFile { + public static final byte[] MAGIC = {0x44, 0x58, 0x44, 0x49, 0x46, 0x46}; // DXDIFF + public static final short CURRENT_VERSION = 0x0002; + private final DexDataBuffer buffer; + private short version; + private int patchedDexSize; + private int firstChunkOffset; + private int patchedStringIdSectionOffset; + private int patchedTypeIdSectionOffset; + private int patchedProtoIdSectionOffset; + private int patchedFieldIdSectionOffset; + private int patchedMethodIdSectionOffset; + private int patchedClassDefSectionOffset; + private int patchedMapListSectionOffset; + private int patchedTypeListSectionOffset; + private int patchedAnnotationSetRefListSectionOffset; + private int patchedAnnotationSetSectionOffset; + private int patchedClassDataSectionOffset; + private int patchedCodeSectionOffset; + private int patchedStringDataSectionOffset; + private int patchedDebugInfoSectionOffset; + private int patchedAnnotationSectionOffset; + private int patchedEncodedArraySectionOffset; + private int patchedAnnotationsDirectorySectionOffset; + private byte[] oldDexSignature; + + public DexPatchFile(File file) throws IOException { + this.buffer = new DexDataBuffer(ByteBuffer.wrap(FileUtils.readFile(file))); + init(); + } + + public DexPatchFile(InputStream is) throws IOException { + this.buffer = new DexDataBuffer(ByteBuffer.wrap(FileUtils.readStream(is))); + init(); + } + + private void init() { + byte[] magic = this.buffer.readByteArray(MAGIC.length); + if (CompareUtils.uArrCompare(magic, MAGIC) != 0) { + throw new IllegalStateException("bad dex patch file magic: " + Arrays.toString(magic)); + } + + this.version = this.buffer.readShort(); + if (CompareUtils.uCompare(this.version, CURRENT_VERSION) != 0) { + throw new IllegalStateException("bad dex patch file version: " + this.version + ", expected: " + CURRENT_VERSION); + } + + this.patchedDexSize = this.buffer.readInt(); + this.firstChunkOffset = this.buffer.readInt(); + this.patchedStringIdSectionOffset = this.buffer.readInt(); + this.patchedTypeIdSectionOffset = this.buffer.readInt(); + this.patchedProtoIdSectionOffset = this.buffer.readInt(); + this.patchedFieldIdSectionOffset = this.buffer.readInt(); + this.patchedMethodIdSectionOffset = this.buffer.readInt(); + this.patchedClassDefSectionOffset = this.buffer.readInt(); + this.patchedMapListSectionOffset = this.buffer.readInt(); + this.patchedTypeListSectionOffset = this.buffer.readInt(); + this.patchedAnnotationSetRefListSectionOffset = this.buffer.readInt(); + this.patchedAnnotationSetSectionOffset = this.buffer.readInt(); + this.patchedClassDataSectionOffset = this.buffer.readInt(); + this.patchedCodeSectionOffset = this.buffer.readInt(); + this.patchedStringDataSectionOffset = this.buffer.readInt(); + this.patchedDebugInfoSectionOffset = this.buffer.readInt(); + this.patchedAnnotationSectionOffset = this.buffer.readInt(); + this.patchedEncodedArraySectionOffset = this.buffer.readInt(); + this.patchedAnnotationsDirectorySectionOffset = this.buffer.readInt(); + this.oldDexSignature = this.buffer.readByteArray(SizeOf.SIGNATURE); + + this.buffer.position(firstChunkOffset); + } + + private List readDeltaIndiciesOrOffsets(int count) { + List result = new ArrayList<>(count); + int lastVal = 0; + for (int i = 0; i < count; ++i) { + int delta = this.buffer.readSleb128(); + lastVal = lastVal + delta; + result.add(lastVal); + } + return result; + } + + private > void readChunkData( + int sectionType, Set deletedItemIndices, Map indexToNewItemMap + ) { + int deletedItemCount = this.buffer.readUleb128(); + List deletedIndices = readDeltaIndiciesOrOffsets(deletedItemCount); + deletedItemIndices.addAll(deletedIndices); + + int addedItemCount = this.buffer.readUleb128(); + List addedIndices = readDeltaIndiciesOrOffsets(addedItemCount); + + int replacedItemCount = this.buffer.readUleb128(); + List replacedIndices = readDeltaIndiciesOrOffsets(replacedItemCount); + + int addedIndexCursor = 0; + int replacedIndexCursor = 0; + + while (addedIndexCursor < addedItemCount || replacedIndexCursor < replacedItemCount) { + if (addedIndexCursor >= addedItemCount) { + // rest items are all replaced item. + while (replacedIndexCursor < replacedItemCount) { + T newItem = readItemBySectionType(sectionType); + indexToNewItemMap.put(replacedIndexCursor, newItem); + ++replacedIndexCursor; + } + } else + if (replacedIndexCursor >= replacedItemCount) { + // rest items are all added item. + while (addedIndexCursor < addedItemCount) { + T newItem = readItemBySectionType(sectionType); + indexToNewItemMap.put(addedIndexCursor, newItem); + ++addedIndexCursor; + } + } else { + T newItem = readItemBySectionType(sectionType); + if (addedIndexCursor <= replacedIndexCursor) { + indexToNewItemMap.put(addedIndexCursor, newItem); + ++addedIndexCursor; + } else { + indexToNewItemMap.put(replacedIndexCursor, newItem); + ++replacedIndexCursor; + } + } + } + } + + @SuppressWarnings("unchecked") + private > T readItemBySectionType(int sectionType) { + switch (sectionType) { + case TableOfContents.SECTION_TYPE_TYPEIDS: { + return (T) (Integer) this.buffer.readInt(); + } + case TableOfContents.SECTION_TYPE_PROTOIDS: { + return (T) this.buffer.readProtoId(); + } + case TableOfContents.SECTION_TYPE_FIELDIDS: { + return (T) this.buffer.readFieldId(); + } + case TableOfContents.SECTION_TYPE_METHODIDS: { + return (T) this.buffer.readMethodId(); + } + case TableOfContents.SECTION_TYPE_CLASSDEFS: { + return (T) this.buffer.readClassDef(); + } + case TableOfContents.SECTION_TYPE_STRINGDATAS: { + return (T) this.buffer.readStringData(); + } + case TableOfContents.SECTION_TYPE_TYPELISTS: { + return (T) this.buffer.readTypeList(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONS: { + return (T) this.buffer.readAnnotation(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETS: { + return (T) this.buffer.readAnnotationSet(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSETREFLISTS: { + return (T) this.buffer.readAnnotationSetRefList(); + } + case TableOfContents.SECTION_TYPE_ANNOTATIONSDIRECTORIES: { + return (T) this.buffer.readAnnotationsDirectory(); + } + case TableOfContents.SECTION_TYPE_DEBUGINFOS: { + return (T) this.buffer.readDebugInfoItem(); + } + case TableOfContents.SECTION_TYPE_CODES: { + return (T) this.buffer.readCode(); + } + case TableOfContents.SECTION_TYPE_CLASSDATA: { + return (T) this.buffer.readClassData(); + } + case TableOfContents.SECTION_TYPE_ENCODEDARRAYS: { + return (T) this.buffer.readEncodedArray(); + } + default: { + return null; + } + } + } + + public short getVersion() { + return version; + } + + public byte[] getOldDexSignature() { + return this.oldDexSignature; + } + + public int getPatchedDexSize() { + return patchedDexSize; + } + + public int getPatchedStringIdSectionOffset() { + return patchedStringIdSectionOffset; + } + + public int getPatchedTypeIdSectionOffset() { + return patchedTypeIdSectionOffset; + } + + public int getPatchedProtoIdSectionOffset() { + return patchedProtoIdSectionOffset; + } + + public int getPatchedFieldIdSectionOffset() { + return patchedFieldIdSectionOffset; + } + + public int getPatchedMethodIdSectionOffset() { + return patchedMethodIdSectionOffset; + } + + public int getPatchedClassDefSectionOffset() { + return patchedClassDefSectionOffset; + } + + public int getPatchedMapListSectionOffset() { + return patchedMapListSectionOffset; + } + + public int getPatchedTypeListSectionOffset() { + return patchedTypeListSectionOffset; + } + + public int getPatchedAnnotationSetRefListSectionOffset() { + return patchedAnnotationSetRefListSectionOffset; + } + + public int getPatchedAnnotationSetSectionOffset() { + return patchedAnnotationSetSectionOffset; + } + + public int getPatchedClassDataSectionOffset() { + return patchedClassDataSectionOffset; + } + + public int getPatchedCodeSectionOffset() { + return patchedCodeSectionOffset; + } + + public int getPatchedStringDataSectionOffset() { + return patchedStringDataSectionOffset; + } + + public int getPatchedDebugInfoSectionOffset() { + return patchedDebugInfoSectionOffset; + } + + public int getPatchedAnnotationSectionOffset() { + return patchedAnnotationSectionOffset; + } + + public int getPatchedEncodedArraySectionOffset() { + return patchedEncodedArraySectionOffset; + } + + public int getPatchedAnnotationsDirectorySectionOffset() { + return patchedAnnotationsDirectorySectionOffset; + } + + public DexDataBuffer getBuffer() { + return buffer; + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/PatchOperation.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/PatchOperation.java new file mode 100644 index 00000000..0f129d9f --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/PatchOperation.java @@ -0,0 +1,63 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.struct; + +/** + * Created by tangyinsheng on 2016/6/29. + */ +public final class PatchOperation { + public static final int OP_DEL = 0; + public static final int OP_ADD = 1; + public static final int OP_REPLACE = 2; + + public int op; + public int index; + public T newItem; + + public PatchOperation(int op, int index) { + this(op, index, null); + } + + public PatchOperation(int op, int index, T newItem) { + this.op = op; + this.index = index; + this.newItem = newItem; + } + + public static String translateOpToString(int op) { + switch (op) { + case OP_DEL: + return "OP_DEL"; + case OP_ADD: + return "OP_ADD"; + case OP_REPLACE: + return "OP_REPLACE"; + default: + return "OP_UNKNOWN"; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String opDesc = translateOpToString(op); + sb.append('{'); + sb.append("op: ").append(opDesc).append(", index: ").append(index).append(", newItem: ").append(newItem); + sb.append('}'); + return sb.toString(); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/SmallPatchedDexItemFile.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/SmallPatchedDexItemFile.java new file mode 100644 index 00000000..fc517591 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/dexpatcher/struct/SmallPatchedDexItemFile.java @@ -0,0 +1,412 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.dexpatcher.struct; + +import com.tencent.tinker.android.dex.SizeOf; +import com.tencent.tinker.android.dex.io.DexDataBuffer; +import com.tencent.tinker.android.dex.util.CompareUtils; +import com.tencent.tinker.android.dex.util.FileUtils; +import com.tencent.tinker.android.dx.util.Hex; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by tangyinsheng on 2016/8/10. + */ +public final class SmallPatchedDexItemFile { + public static final byte[] MAGIC = {0x44, 0x44, 0x45, 0x58, 0x54, 0x52, 0x41}; // DDEXTRA + public static final short CURRENT_VERSION = 0x0001; + private final List oldDexSigns = new ArrayList<>(); + private final Map + oldDexSignToPatchedStringIdOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedTypeIdOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedProtoIdOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedFieldIdOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedMethodIdOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedClassDefOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedMapListOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedTypeListOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedAnnotationSetRefListOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedAnnotationSetOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedClassDataOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedCodeOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedStringDataOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedDebugInfoOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedAnnotationOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedEncodedArrayOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedAnnotationsDirectoryOffsetMap = new HashMap<>(); + private final Map + oldDexSignToPatchedDexSizeMap = new HashMap<>(); + private final Map> + oldDexSignToStringIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToTypeIdIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToTypeListIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToProtoIdIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToFieldIdIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToMethodIdIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToAnnotationIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToAnnotationSetIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToAnnotationSetRefListIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToAnnotationsDirectoryIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToEncodedArrayIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToDebugInfoIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToCodeIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToClassDataIndicesInSmallPatch = new HashMap<>(); + private final Map> + oldDexSignToClassDefIndicesInSmallPatch = new HashMap<>(); + private int version; + private int firstChunkOffset; + + public SmallPatchedDexItemFile(File input) throws IOException { + DexDataBuffer buffer = new DexDataBuffer(ByteBuffer.wrap(FileUtils.readFile(input))); + init(buffer); + } + + public SmallPatchedDexItemFile(InputStream is) throws IOException { + DexDataBuffer buffer = new DexDataBuffer(ByteBuffer.wrap(FileUtils.readStream(is))); + init(buffer); + } + + private void init(DexDataBuffer buffer) throws IOException { + byte[] magic = buffer.readByteArray(MAGIC.length); + if (CompareUtils.uArrCompare(magic, MAGIC) != 0) { + throw new IllegalStateException( + "bad dexdiff extra file magic: " + Arrays.toString(magic) + ); + } + this.version = buffer.readShort(); + if (this.version != CURRENT_VERSION) { + throw new IllegalStateException( + "bad dexdiff extra file version: " + this.version + ", expected: " + CURRENT_VERSION + ); + } + + this.firstChunkOffset = buffer.readInt(); + buffer.position(this.firstChunkOffset); + + int oldDexSignCount = buffer.readUleb128(); + for (int i = 0; i < oldDexSignCount; ++i) { + byte[] oldDexSign = buffer.readByteArray(SizeOf.SIGNATURE); + oldDexSigns.add(Hex.toHexString(oldDexSign)); + } + + for (int i = 0; i < oldDexSignCount; ++i) { + final String oldDexSign = oldDexSigns.get(i); + oldDexSignToPatchedStringIdOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedTypeIdOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedProtoIdOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedFieldIdOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedMethodIdOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedClassDefOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedStringDataOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedTypeListOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedAnnotationOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedAnnotationSetOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedAnnotationSetRefListOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedAnnotationsDirectoryOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedDebugInfoOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedCodeOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedClassDataOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedEncodedArrayOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedMapListOffsetMap.put(oldDexSign, buffer.readInt()); + oldDexSignToPatchedDexSizeMap.put(oldDexSign, buffer.readInt()); + } + + readDataChunk(buffer, oldDexSignToStringIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToTypeIdIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToTypeListIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToProtoIdIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToFieldIdIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToMethodIdIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToAnnotationIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToAnnotationSetIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToAnnotationSetRefListIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToAnnotationsDirectoryIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToEncodedArrayIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToDebugInfoIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToCodeIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToClassDataIndicesInSmallPatch); + readDataChunk(buffer, oldDexSignToClassDefIndicesInSmallPatch); + } + + private void readDataChunk( + DexDataBuffer buffer, Map> oldDexSignToIndicesInSmallPatchMap + ) { + int oldDexSignCount = oldDexSigns.size(); + for (int i = 0; i < oldDexSignCount; ++i) { + int itemCount = buffer.readUleb128(); + int prevIndex = 0; + for (int j = 0; j < itemCount; ++j) { + int indexDelta = buffer.readSleb128(); + prevIndex += indexDelta; + + final String oldDexSign = oldDexSigns.get(i); + Set indices = oldDexSignToIndicesInSmallPatchMap.get(oldDexSign); + if (indices == null) { + indices = new HashSet<>(); + oldDexSignToIndicesInSmallPatchMap.put(oldDexSign, indices); + } + + indices.add(prevIndex); + } + } + } + + public boolean isAffectedOldDex(String oldDexSign) { + return this.oldDexSigns.contains(oldDexSign); + } + + public boolean isSmallPatchedDexEmpty(String oldDexSign) { + Set indices = this.oldDexSignToClassDefIndicesInSmallPatch.get(oldDexSign); + return (indices == null || indices.isEmpty()); + } + + public int getPatchedStringIdOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedStringIdOffsetMap.get(oldDexSign); + } + + public int getPatchedTypeIdOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedTypeIdOffsetMap.get(oldDexSign); + } + + public int getPatchedProtoIdOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedProtoIdOffsetMap.get(oldDexSign); + } + + public int getPatchedFieldIdOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedFieldIdOffsetMap.get(oldDexSign); + } + + public int getPatchedMethodIdOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedMethodIdOffsetMap.get(oldDexSign); + } + + public int getPatchedClassDefOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedClassDefOffsetMap.get(oldDexSign); + } + + public int getPatchedMapListOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedMapListOffsetMap.get(oldDexSign); + } + + public int getPatchedTypeListOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedTypeListOffsetMap.get(oldDexSign); + } + + public int getPatchedAnnotationSetRefListOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedAnnotationSetRefListOffsetMap.get(oldDexSign); + } + + public int getPatchedAnnotationSetOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedAnnotationSetOffsetMap.get(oldDexSign); + } + + public int getPatchedClassDataOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedClassDataOffsetMap.get(oldDexSign); + } + + public int getPatchedCodeOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedCodeOffsetMap.get(oldDexSign); + } + + public int getPatchedStringDataOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedStringDataOffsetMap.get(oldDexSign); + } + + public int getPatchedDebugInfoOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedDebugInfoOffsetMap.get(oldDexSign); + } + + public int getPatchedAnnotationOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedAnnotationOffsetMap.get(oldDexSign); + } + + public int getPatchedEncodedArrayOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedEncodedArrayOffsetMap.get(oldDexSign); + } + + public int getPatchedAnnotationsDirectoryOffsetByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedAnnotationsDirectoryOffsetMap.get(oldDexSign); + } + + public int getPatchedDexSizeByOldDexSign(String oldDexSign) { + return this.oldDexSignToPatchedDexSizeMap.get(oldDexSign); + } + + public boolean isStringInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToStringIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isTypeIdInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToTypeIdIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isTypeListInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToTypeListIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isProtoIdInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToProtoIdIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isFieldIdInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToFieldIdIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isMethodIdInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToMethodIdIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isAnnotationInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToAnnotationIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isAnnotationSetInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToAnnotationSetIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isAnnotationSetRefListInSmallPatchedDex( + String oldDexSign, int indexInPatchedDex + ) { + Set indices = oldDexSignToAnnotationSetRefListIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isAnnotationsDirectoryInSmallPatchedDex( + String oldDexSign, int indexInPatchedDex + ) { + Set indices = oldDexSignToAnnotationsDirectoryIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isEncodedArrayInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToEncodedArrayIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isDebugInfoInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToDebugInfoIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isCodeInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToCodeIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isClassDataInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToClassDataIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } + + public boolean isClassDefInSmallPatchedDex(String oldDexSign, int indexInPatchedDex) { + Set indices = oldDexSignToClassDefIndicesInSmallPatch.get(oldDexSign); + if (indices == null) { + return false; + } + return indices.contains(indexInPatchedDex); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/resutil/ResUtil.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/resutil/ResUtil.java new file mode 100644 index 00000000..b9780090 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/resutil/ResUtil.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.resutil; + +import com.tencent.tinker.commons.ziputil.TinkerZipEntry; +import com.tencent.tinker.commons.ziputil.TinkerZipFile; +import com.tencent.tinker.commons.ziputil.TinkerZipOutputStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by zhangshaowen on 16/8/10. + */ +public class ResUtil { + private static final int BUFFER_SIZE = 16384; + + public static void extractTinkerEntry(TinkerZipFile apk, TinkerZipEntry zipEntry, TinkerZipOutputStream outputStream) throws IOException { + InputStream in = null; + try { + in = apk.getInputStream(zipEntry); + outputStream.putNextEntry(new TinkerZipEntry(zipEntry)); + byte[] buffer = new byte[BUFFER_SIZE]; + + for (int length = in.read(buffer); length != -1; length = in.read(buffer)) { + outputStream.write(buffer, 0, length); + } + outputStream.closeEntry(); + } finally { + if (in != null) { + in.close(); + } + } + } + + public static void extractLargeModifyFile(TinkerZipEntry sourceArscEntry, File newFile, long newFileCrc, TinkerZipOutputStream outputStream) throws IOException { + TinkerZipEntry newArscZipEntry = new TinkerZipEntry(sourceArscEntry); + + newArscZipEntry.setMethod(TinkerZipEntry.STORED); + newArscZipEntry.setSize(newFile.length()); + newArscZipEntry.setCompressedSize(newFile.length()); + newArscZipEntry.setCrc(newFileCrc); + FileInputStream in = null; + try { + in = new FileInputStream(newFile); + outputStream.putNextEntry(new TinkerZipEntry(newArscZipEntry)); + byte[] buffer = new byte[BUFFER_SIZE]; + + for (int length = in.read(buffer); length != -1; length = in.read(buffer)) { + outputStream.write(buffer, 0, length); + } + outputStream.closeEntry(); + } finally { + if (in != null) { + in.close(); + } + } + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Arrays.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Arrays.java new file mode 100644 index 00000000..8712e839 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Arrays.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public class Arrays { + public static void checkOffsetAndCount(int arrayLength, int offset, int count) { + if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { +// throw new ArrayIndexOutOfBoundsException(arrayLength, offset, +// count); + throw new ArrayIndexOutOfBoundsException(offset); + } + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/BufferIterator.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/BufferIterator.java new file mode 100644 index 00000000..7c175e54 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/BufferIterator.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public abstract class BufferIterator { + /** + * Seeks to the absolute position {@code offset}, measured in bytes from the start. + */ + public abstract void seek(int offset); + /** + * Skips forwards or backwards {@code byteCount} bytes from the current position. + */ + public abstract void skip(int byteCount); + + public abstract int readInt(); + + public abstract short readShort(); +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/HeapBufferIterator.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/HeapBufferIterator.java new file mode 100644 index 00000000..d6591ce8 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/HeapBufferIterator.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.nio.ByteOrder; + +/** + * Iterates over big- or little-endian bytes in a Java byte[]. + * + * @hide don't make this public without adding bounds checking. + */ +public final class HeapBufferIterator extends BufferIterator { + private final byte[] buffer; + private final int offset; + private final int byteCount; + private final ByteOrder order; + private int position; + HeapBufferIterator(byte[] buffer, int offset, int byteCount, ByteOrder order) { + this.buffer = buffer; + this.offset = offset; + this.byteCount = byteCount; + this.order = order; + } + + /** + * Returns a new iterator over {@code buffer}, starting at {@code offset} and continuing for + * {@code byteCount} bytes. Items larger than a byte are interpreted using the given byte order. + */ + public static BufferIterator iterator(byte[] buffer, int offset, int byteCount, ByteOrder order) { + return new HeapBufferIterator(buffer, offset, byteCount, order); + } + + public void seek(int offset) { + position = offset; + } + + public void skip(int byteCount) { + position += byteCount; + } + + public void readByteArray(byte[] dst, int dstOffset, int byteCount) { + System.arraycopy(buffer, offset + position, dst, dstOffset, byteCount); + position += byteCount; + } + + public byte readByte() { + byte result = buffer[offset + position]; + ++position; + return result; + } + + public int readInt() { + int result = Memory.peekInt(buffer, offset + position, order); + position += SizeOf.INT; + return result; + } + + public short readShort() { + short result = Memory.peekShort(buffer, offset + position, order); + position += SizeOf.SHORT; + return result; + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Memory.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Memory.java new file mode 100644 index 00000000..cf7b75dc --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Memory.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.nio.ByteOrder; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public final class Memory { + private Memory() { } + + public static int peekInt(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + return (((src[offset++] & 0xff) << 24) + | ((src[offset++] & 0xff) << 16) + | ((src[offset++] & 0xff) << 8) + | ((src[offset ] & 0xff) << 0)); + } else { + return (((src[offset++] & 0xff) << 0) + | ((src[offset++] & 0xff) << 8) + | ((src[offset++] & 0xff) << 16) + | ((src[offset ] & 0xff) << 24)); + } + } + public static long peekLong(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + int h = ((src[offset++] & 0xff) << 24) + | ((src[offset++] & 0xff) << 16) + | ((src[offset++] & 0xff) << 8) + | ((src[offset++] & 0xff) << 0); + int l = ((src[offset++] & 0xff) << 24) + | ((src[offset++] & 0xff) << 16) + | ((src[offset++] & 0xff) << 8) + | ((src[offset ] & 0xff) << 0); + return (((long) h) << 32L) | ((long) l) & 0xffffffffL; + } else { + int l = ((src[offset++] & 0xff) << 0) + | ((src[offset++] & 0xff) << 8) + | ((src[offset++] & 0xff) << 16) + | ((src[offset++] & 0xff) << 24); + int h = ((src[offset++] & 0xff) << 0) + | ((src[offset++] & 0xff) << 8) + | ((src[offset++] & 0xff) << 16) + | ((src[offset ] & 0xff) << 24); + return (((long) h) << 32L) | ((long) l) & 0xffffffffL; + } + } + public static short peekShort(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + return (short) ((src[offset] << 8) | (src[offset + 1] & 0xff)); + } else { + return (short) ((src[offset + 1] << 8) | (src[offset] & 0xff)); + } + } + public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + dst[offset++] = (byte) ((value >> 24) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset ] = (byte) ((value >> 0) & 0xff); + } else { + dst[offset++] = (byte) ((value >> 0) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset ] = (byte) ((value >> 24) & 0xff); + } + } + public static void pokeLong(byte[] dst, int offset, long value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + int i = (int) (value >> 32); + dst[offset++] = (byte) ((i >> 24) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 0) & 0xff); + i = (int) value; + dst[offset++] = (byte) ((i >> 24) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset ] = (byte) ((i >> 0) & 0xff); + } else { + int i = (int) value; + dst[offset++] = (byte) ((i >> 0) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 24) & 0xff); + i = (int) (value >> 32); + dst[offset++] = (byte) ((i >> 0) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset ] = (byte) ((i >> 24) & 0xff); + } + } + public static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset ] = (byte) ((value >> 0) & 0xff); + } else { + dst[offset++] = (byte) ((value >> 0) & 0xff); + dst[offset ] = (byte) ((value >> 8) & 0xff); + } + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/SizeOf.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/SizeOf.java new file mode 100644 index 00000000..03458938 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/SizeOf.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public final class SizeOf { + public static final int CHAR = 2; + public static final int DOUBLE = 8; + public static final int FLOAT = 4; + public static final int INT = 4; + public static final int LONG = 8; + public static final int SHORT = 2; + private SizeOf() { + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/StandardCharsets.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/StandardCharsets.java new file mode 100644 index 00000000..caa1fbf4 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/StandardCharsets.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.nio.charset.Charset; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public final class StandardCharsets { + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + private StandardCharsets() { + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Streams.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Streams.java new file mode 100644 index 00000000..4af128a4 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/Streams.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.util.concurrent.atomic.AtomicReference; + +//import java.util.Arrays; + +/** + * modify by zhangshaowen on 16/6/7. + */ +public final class Streams { + private static AtomicReference skipBuffer = new AtomicReference(); + private Streams() { + } + /** + * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int). + * InputStream assumes that you implement InputStream.read(int) and provides default + * implementations of the others, but often the opposite is more efficient. + */ + public static int readSingleByte(InputStream in) throws IOException { + byte[] buffer = new byte[1]; + int result = in.read(buffer, 0, 1); + return (result != -1) ? buffer[0] & 0xff : -1; + } + /** + * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int). + * OutputStream assumes that you implement OutputStream.write(int) and provides default + * implementations of the others, but often the opposite is more efficient. + */ + public static void writeSingleByte(OutputStream out, int b) throws IOException { + byte[] buffer = new byte[1]; + buffer[0] = (byte) (b & 0xff); + out.write(buffer); + } + /** + * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available. + */ + public static void readFully(InputStream in, byte[] dst) throws IOException { + readFully(in, dst, 0, dst.length); + } + + /** + * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws + * EOFException if insufficient bytes are available. + * + * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}. + */ + public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException { + if (byteCount == 0) { + return; + } + if (in == null) { + throw new NullPointerException("in == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + Arrays.checkOffsetAndCount(dst.length, offset, byteCount); + while (byteCount > 0) { + int bytesRead = in.read(dst, offset, byteCount); + if (bytesRead < 0) { + throw new EOFException(); + } + offset += bytesRead; + byteCount -= bytesRead; + } + } + /** + * Returns a byte[] containing the remainder of 'in', closing it when done. + */ + public static byte[] readFully(InputStream in) throws IOException { + try { + return readFullyNoClose(in); + } finally { + in.close(); + } + } + /** + * Returns a byte[] containing the remainder of 'in'. + */ + public static byte[] readFullyNoClose(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } + /** + * Returns the remainder of 'reader' as a string, closing it when done. + */ + public static String readFully(Reader reader) throws IOException { + try { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } finally { + reader.close(); + } + } + public static void skipAll(InputStream in) throws IOException { + do { + in.skip(Long.MAX_VALUE); + } while (in.read() != -1); + } + /** + * Skip at most {@code byteCount} bytes from {@code in} by calling read + * repeatedly until either the stream is exhausted or we read fewer bytes than + * we ask for. + * + *

This method reuses the skip buffer but is careful to never use it at + * the same time that another stream is using it. Otherwise streams that use + * the caller's buffer for consistency checks like CRC could be clobbered by + * other threads. A thread-local buffer is also insufficient because some + * streams may call other streams in their skip() method, also clobbering the + * buffer. + */ + public static long skipByReading(InputStream in, long byteCount) throws IOException { + // acquire the shared skip buffer. + byte[] buffer = skipBuffer.getAndSet(null); + if (buffer == null) { + buffer = new byte[4096]; + } + long skipped = 0; + while (skipped < byteCount) { + int toRead = (int) Math.min(byteCount - skipped, buffer.length); + int read = in.read(buffer, 0, toRead); + if (read == -1) { + break; + } + skipped += read; + if (read < toRead) { + break; + } + } + // release the shared skip buffer. + skipBuffer.set(buffer); + return skipped; + } + /** + * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed. + * Returns the total number of bytes transferred. + */ + public static int copy(InputStream in, OutputStream out) throws IOException { + int total = 0; + byte[] buffer = new byte[8192]; + int c; + while ((c = in.read(buffer)) != -1) { + total += c; + out.write(buffer, 0, c); + } + return total; + } + /** + * Returns the ASCII characters up to but not including the next "\r\n", or + * "\n". + * + * @throws EOFException if the stream is exhausted before the next newline + * character. + */ + public static String readAsciiLine(InputStream in) throws IOException { + // TODO: support UTF-8 here instead + StringBuilder result = new StringBuilder(80); + while (true) { + int c = in.read(); + if (c == -1) { + throw new EOFException(); + } else if (c == '\n') { + break; + } + result.append((char) c); + } + int length = result.length(); + if (length > 0 && result.charAt(length - 1) == '\r') { + result.setLength(length - 1); + } + return result.toString(); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipEntry.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipEntry.java new file mode 100644 index 00000000..a1f03235 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipEntry.java @@ -0,0 +1,449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.zip.ZipException; + +/** + * modify by zhangshaowen on 16/6/7. + * remove zip64 + * + * An entry within a zip file. + * An entry has attributes such as its name (which is actually a path) and the uncompressed size + * of the corresponding data. An entry does not contain the data itself, but can be used as a key + * with {@link TinkerZipFile#getInputStream}. The class documentation for {@code ZipInputStream} and + * {@link TinkerZipOutputStream} shows how {@code ZipEntry} is used in conjunction with those two classes. + */ +public class TinkerZipEntry implements ZipConstants, Cloneable { + /** + * Zip entry state: Deflated. + */ + public static final int DEFLATED = 8; + /** + * Zip entry state: Stored. + */ + public static final int STORED = 0; + String name; + String comment; + long crc = -1; // Needs to be a long to distinguish -1 ("not set") from the 0xffffffff CRC32. + long compressedSize = -1; + long size = -1; + int compressionMethod = -1; + int time = -1; + int modDate = -1; + byte[] extra; + long localHeaderRelOffset = -1; + long dataOffset = -1; + /** @hide - for testing only */ + public TinkerZipEntry(String name, String comment, long crc, long compressedSize, + long size, int compressionMethod, int time, int modDate, byte[] extra, + long localHeaderRelOffset, long dataOffset) { + this.name = name; + this.comment = comment; + this.crc = crc; + this.compressedSize = compressedSize; + this.size = size; + this.compressionMethod = compressionMethod; + this.time = time; + this.modDate = modDate; + this.extra = extra; + this.localHeaderRelOffset = localHeaderRelOffset; + this.dataOffset = dataOffset; + } + /** + * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path, + * and may contain {@code /} characters. + * + * @throws IllegalArgumentException + * if the name length is outside the range (> 0xFFFF). + */ + public TinkerZipEntry(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + validateStringLength("Name", name); + this.name = name; + } + /** + * Constructs a new {@code ZipEntry} using the values obtained from {@code + * ze}. + * + * @param ze + * the {@code ZipEntry} from which to obtain values. + */ + public TinkerZipEntry(TinkerZipEntry ze) { + name = ze.name; + comment = ze.comment; + time = ze.time; + size = ze.size; + compressedSize = ze.compressedSize; + crc = ze.crc; + compressionMethod = ze.compressionMethod; + modDate = ze.modDate; + extra = ze.extra; + localHeaderRelOffset = ze.localHeaderRelOffset; + dataOffset = ze.dataOffset; + } + /* + * Internal constructor. Creates a new ZipEntry by reading the + * Central Directory Entry (CDE) from "in", which must be positioned + * at the CDE signature. If the GPBF_UTF8_FLAG is set in the CDE then + * UTF-8 is used to decode the string information, otherwise the + * defaultCharset is used. + * + * On exit, "in" will be positioned at the start of the next entry + * in the Central Directory. + */ + TinkerZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset, boolean isZip64) throws IOException { + Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length); + BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length, + ByteOrder.LITTLE_ENDIAN); + int sig = it.readInt(); + if (sig != CENSIG) { + TinkerZipFile.throwZipException("unknown", cdStream.available(), "unknown", 0, "Central Directory Entry", sig); + } + it.seek(8); + int gpbf = it.readShort() & 0xffff; + if ((gpbf & TinkerZipFile.GPBF_UNSUPPORTED_MASK) != 0) { + throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf); + } + // If the GPBF_UTF8_FLAG is set then the character encoding is UTF-8 whatever the default + // provided. + Charset charset = defaultCharset; + if ((gpbf & TinkerZipFile.GPBF_UTF8_FLAG) != 0) { + charset = Charset.forName("UTF-8"); + } + compressionMethod = it.readShort() & 0xffff; + time = it.readShort() & 0xffff; + modDate = it.readShort() & 0xffff; + // These are 32-bit values in the file, but 64-bit fields in this object. + crc = ((long) it.readInt()) & 0xffffffffL; + compressedSize = ((long) it.readInt()) & 0xffffffffL; + size = ((long) it.readInt()) & 0xffffffffL; + int nameLength = it.readShort() & 0xffff; + int extraLength = it.readShort() & 0xffff; + int commentByteCount = it.readShort() & 0xffff; + // This is a 32-bit value in the file, but a 64-bit field in this object. + it.seek(42); + localHeaderRelOffset = ((long) it.readInt()) & 0xffffffffL; + byte[] nameBytes = new byte[nameLength]; + Streams.readFully(cdStream, nameBytes, 0, nameBytes.length); + if (containsNulByte(nameBytes)) { + throw new ZipException("Filename contains NUL byte: " + Arrays.toString(nameBytes)); + } + name = new String(nameBytes, 0, nameBytes.length, charset); + if (extraLength > 0) { + extra = new byte[extraLength]; + Streams.readFully(cdStream, extra, 0, extraLength); + } + if (commentByteCount > 0) { + byte[] commentBytes = new byte[commentByteCount]; + Streams.readFully(cdStream, commentBytes, 0, commentByteCount); + comment = new String(commentBytes, 0, commentBytes.length, charset); + } + /*if (isZip64) { + Zip64.parseZip64ExtendedInfo(this, true *//* from central directory *//*); + }*/ + } + + private static boolean containsNulByte(byte[] bytes) { + for (byte b : bytes) { + if (b == 0) { + return true; + } + } + return false; + } + + private static void validateStringLength(String argument, String string) { + // This check is not perfect: the character encoding is determined when the entry is + // written out. UTF-8 is probably a worst-case: most alternatives should be single byte per + // character. + byte[] bytes = string.getBytes(Charset.forName("UTF-8")); + if (bytes.length > 0xffff) { + throw new IllegalArgumentException(argument + " too long: " + bytes.length); + } + } + + /** + * Returns the comment for this {@code ZipEntry}, or {@code null} if there is no comment. + * If we're reading a zip file using {@code ZipInputStream}, the comment is not available. + */ + public String getComment() { + return comment; + } + + /** + * Sets the comment for this {@code ZipEntry}. + * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes. + */ + public void setComment(String comment) { + if (comment == null) { + this.comment = null; + return; + } + validateStringLength("Comment", comment); + this.comment = comment; + } + + /** + * Gets the compressed size of this {@code ZipEntry}. + * + * @return the compressed size, or -1 if the compressed size has not been + * set. + */ + public long getCompressedSize() { + return compressedSize; + } + + /** + * Sets the compressed size for this {@code ZipEntry}. + * + * @param value + * the compressed size (in bytes). + */ + public void setCompressedSize(long value) { + compressedSize = value; + } + + /** + * Gets the checksum for this {@code ZipEntry}. + * + * @return the checksum, or -1 if the checksum has not been set. + */ + public long getCrc() { + return crc; + } + + /** + * Sets the checksum for this {@code ZipEntry}. + * + * @param value + * the checksum for this entry. + * @throws IllegalArgumentException + * if {@code value} is < 0 or > 0xFFFFFFFFL. + */ + public void setCrc(long value) { + if (value >= 0 && value <= 0xFFFFFFFFL) { + crc = value; + } else { + throw new IllegalArgumentException("Bad CRC32: " + value); + } + } + + /** + * Gets the extra information for this {@code ZipEntry}. + * + * @return a byte array containing the extra information, or {@code null} if + * there is none. + */ + public byte[] getExtra() { + return extra; + } + + /** + * Sets the extra information for this {@code ZipEntry}. + * + * @throws IllegalArgumentException if the data length >= 64 KiB. + */ + public void setExtra(byte[] data) { + if (data != null && data.length > 0xffff) { + throw new IllegalArgumentException("Extra data too long: " + data.length); + } + extra = data; + } + + /** + * Gets the compression method for this {@code ZipEntry}. + * + * @return the compression method, either {@code DEFLATED}, {@code STORED} + * or -1 if the compression method has not been set. + */ + public int getMethod() { + return compressionMethod; + } + + /** + * Sets the compression method for this entry to either {@code DEFLATED} or {@code STORED}. + * The default is {@code DEFLATED}, which will cause the size, compressed size, and CRC to be + * set automatically, and the entry's data to be compressed. If you switch to {@code STORED} + * note that you'll have to set the size (or compressed size; they must be the same, but it's + * okay to only set one) and CRC yourself because they must appear before the user data + * in the resulting zip file. See {@link #setSize} and {@link #setCrc}. + * @throws IllegalArgumentException + * when value is not {@code DEFLATED} or {@code STORED}. + */ + public void setMethod(int value) { + if (value != STORED && value != DEFLATED) { + throw new IllegalArgumentException("Bad method: " + value); + } + compressionMethod = value; + } + + /** + * Gets the name of this {@code ZipEntry}. + * + *

Security note: Entry names can represent relative paths. {@code foo/../bar} or + * {@code ../bar/baz}, for example. If the entry name is being used to construct a filename + * or as a path component, it must be validated or sanitized to ensure that files are not + * written outside of the intended destination directory. + * + * @return the entry name. + */ + public String getName() { + return name; + } + + /** + * Gets the uncompressed size of this {@code ZipEntry}. + * + * @return the uncompressed size, or {@code -1} if the size has not been + * set. + */ + public long getSize() { + return size; + } + + /** + * Sets the uncompressed size of this {@code ZipEntry}. + * + * @param value the uncompressed size for this entry. + * @throws IllegalArgumentException if {@code value < 0}. + */ + public void setSize(long value) { + if (value < 0) { + throw new IllegalArgumentException("Bad size: " + value); + } + size = value; + } + + /** + * Gets the last modification time of this {@code ZipEntry}. + * + * @return the last modification time as the number of milliseconds since + * Jan. 1, 1970. + */ + public long getTime() { + if (time != -1) { + GregorianCalendar cal = new GregorianCalendar(); + cal.set(Calendar.MILLISECOND, 0); + cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1, + modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f, + (time & 0x1f) << 1); + return cal.getTime().getTime(); + } + return -1; + } + + /** + * Sets the modification time of this {@code ZipEntry}. + * + * @param value + * the modification time as the number of milliseconds since Jan. + * 1, 1970. + */ + public void setTime(long value) { + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(new Date(value)); + int year = cal.get(Calendar.YEAR); + if (year < 1980) { + modDate = 0x21; + time = 0; + } else { + modDate = cal.get(Calendar.DATE); + modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate; + modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate; + time = cal.get(Calendar.SECOND) >> 1; + time = (cal.get(Calendar.MINUTE) << 5) | time; + time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time; + } + } + + /** + * Determine whether or not this {@code ZipEntry} is a directory. + * + * @return {@code true} when this {@code ZipEntry} is a directory, {@code + * false} otherwise. + */ + public boolean isDirectory() { + return name.charAt(name.length() - 1) == '/'; + } + + /** @hide */ + public long getDataOffset() { + return dataOffset; + } + + /** @hide */ + public void setDataOffset(long value) { + dataOffset = value; + } + + /** + * Returns the string representation of this {@code ZipEntry}. + * + * @return the string representation of this {@code ZipEntry}. + */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("name:" + name); + sb.append("\ncomment:" + comment); + sb.append("\ntime:" + time); + sb.append("\nsize:" + size); + sb.append("\ncompressedSize:" + compressedSize); + sb.append("\ncrc:" + crc); + sb.append("\ncompressionMethod:" + compressionMethod); + sb.append("\nmodDate:" + modDate); + sb.append("\nextra:" + extra); + sb.append("\nlocalHeaderRelOffset:" + localHeaderRelOffset); + sb.append("\ndataOffset:" + dataOffset); + return sb.toString(); + } + + /** + * Returns a deep copy of this zip entry. + */ + @Override public Object clone() { + try { + TinkerZipEntry result = (TinkerZipEntry) super.clone(); + result.extra = extra != null ? extra.clone() : null; + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + /** + * Returns the hash code for this {@code ZipEntry}. + * + * @return the hash code of the entry. + */ + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipFile.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipFile.java new file mode 100644 index 00000000..dbc8f48b --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipFile.java @@ -0,0 +1,605 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +// import libcore.io.IoUtils; + +/** + * modify by zhangshaowen on 16/6/7. + * + * This class provides random read access to a zip file. You pay more to read + * the zip file's central directory up front (from the constructor), but if you're using + * {@link #getEntry} to look up multiple files by name, you get the benefit of this index. + * + *

If you only want to iterate through all the files (using {@link #entries()}, you should + * consider {@link ZipInputStream}, which provides stream-like read access to a zip file and + * has a lower up-front cost because you don't pay to build an in-memory index. + * + *

If you want to create a zip file, use {@link ZipOutputStream}. There is no API for updating + * an existing zip file. + */ +public class TinkerZipFile implements Closeable, ZipConstants { + /** + * Open zip file for reading. + */ + public static final int OPEN_READ = 1; + /** + * Delete zip file when closed. + */ + public static final int OPEN_DELETE = 4; + /** + * General Purpose Bit Flags, Bit 0. + * If set, indicates that the file is encrypted. + */ + static final int GPBF_ENCRYPTED_FLAG = 1 << 0; + /** + * General Purpose Bit Flags, Bit 3. + * If this bit is set, the fields crc-32, compressed + * size and uncompressed size are set to zero in the + * local header. The correct values are put in the + * data descriptor immediately following the compressed + * data. (Note: PKZIP version 2.04g for DOS only + * recognizes this bit for method 8 compression, newer + * versions of PKZIP recognize this bit for any + * compression method.) + */ + static final int GPBF_DATA_DESCRIPTOR_FLAG = 1 << 3; + /** + * General Purpose Bit Flags, Bit 11. + * Language encoding flag (EFS). If this bit is set, + * the filename and comment fields for this file + * must be encoded using UTF-8. + */ + static final int GPBF_UTF8_FLAG = 1 << 11; + /** + * Supported General Purpose Bit Flags Mask. + * Bit mask of bits not supported. + * Note: The only bit that we will enforce at this time + * is the encrypted bit. Although other bits are not supported, + * we must not enforce them as this could break some legitimate + * use cases (See http://b/8617715). + */ + static final int GPBF_UNSUPPORTED_MASK = GPBF_ENCRYPTED_FLAG; + private final String filename; + private final LinkedHashMap entries = new LinkedHashMap(); + private File fileToDeleteOnClose; + private RandomAccessFile raf; + private String comment; + + /** + * Constructs a new {@code ZipFile} allowing read access to the contents of the given file. + * + *

UTF-8 is used to decode all comments and entry names in the file. + * + * @throws ZipException if a zip error occurs. + * @throws IOException if an {@code IOException} occurs. + */ + public TinkerZipFile(File file) throws ZipException, IOException { + this(file, OPEN_READ); + } + /** + * Constructs a new {@code ZipFile} allowing read access to the contents of the given file. + * + *

UTF-8 is used to decode all comments and entry names in the file. + * + * @throws IOException if an IOException occurs. + */ + public TinkerZipFile(String name) throws IOException { + this(new File(name), OPEN_READ); + } + /** + * Constructs a new {@code ZipFile} allowing access to the given file. + * + *

UTF-8 is used to decode all comments and entry names in the file. + * + *

The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}. + * If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the + * time that the {@code ZipFile} is closed (the contents will remain accessible until + * this {@code ZipFile} is closed); it also calls {@code File.deleteOnExit}. + * + * @throws IOException if an {@code IOException} occurs. + */ + public TinkerZipFile(File file, int mode) throws IOException { + filename = file.getPath(); + if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE)) { + throw new IllegalArgumentException("Bad mode: " + mode); + } + if ((mode & OPEN_DELETE) != 0) { + fileToDeleteOnClose = file; + fileToDeleteOnClose.deleteOnExit(); + } else { + fileToDeleteOnClose = null; + } + raf = new RandomAccessFile(filename, "r"); + + readCentralDir(); + // guard.open("close"); + } + + /** + * Returns true if the string is null or 0-length. + * @param str the string to be examined + * @return true if str is null or zero length + */ + public static boolean isEmpty(CharSequence str) { + if (str == null || str.length() == 0) { + return true; + } + return false; + } + /*@Override protected void finalize() throws IOException { + try { + if (guard != null) { + guard.warnIfOpen(); + } + } finally { + try { + super.finalize(); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + }*/ + + /** + * Returns true if a and b are equal, including if they are both null. + *

Note: In platform versions 1.1 and earlier, this method only worked well if + * both the arguments were instances of String.

+ * @param a first CharSequence to check + * @param b second CharSequence to check + * @return true if a and b are equal + */ + public static boolean equals(CharSequence a, CharSequence b) { + if (a == b) return true; + int length; + if (a != null && b != null && (length = a.length()) == b.length()) { + if (a instanceof String && b instanceof String) { + return a.equals(b); + } else { + for (int i = 0; i < length; i++) { + if (a.charAt(i) != b.charAt(i)) return false; + } + return true; + } + } + return false; + } + + private static EocdRecord parseEocdRecord(RandomAccessFile raf, long offset, boolean isZip64) throws IOException { + raf.seek(offset); + // Read the End Of Central Directory. ENDHDR includes the signature bytes, + // which we've already read. + byte[] eocd = new byte[ENDHDR - 4]; + raf.readFully(eocd); + BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN); + final long numEntries; + final long centralDirOffset; + if (isZip64) { + numEntries = -1; + centralDirOffset = -1; + // If we have a zip64 end of central directory record, we skip through the regular + // end of central directory record and use the information from the zip64 eocd record. + // We're still forced to read the comment length (below) since it isn't present in the + // zip64 eocd record. + it.skip(16); + } else { + // If we don't have a zip64 eocd record, we read values from the "regular" + // eocd record. + int diskNumber = it.readShort() & 0xffff; + int diskWithCentralDir = it.readShort() & 0xffff; + numEntries = it.readShort() & 0xffff; + int totalNumEntries = it.readShort() & 0xffff; + it.skip(4); // Ignore centralDirSize. + centralDirOffset = ((long) it.readInt()) & 0xffffffffL; + if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) { + throw new ZipException("Spanned archives not supported"); + } + } + final int commentLength = it.readShort() & 0xffff; + return new EocdRecord(numEntries, centralDirOffset, commentLength); + } + + static void throwZipException(String filename, long fileSize, String entryName, long localHeaderRelOffset, String msg, int magic) throws ZipException { + final String hexString = Integer.toHexString(magic); + throw new ZipException("file name:" + filename + + ", file size" + fileSize + + ", entry name:" + entryName + + ", entry localHeaderRelOffset:" + localHeaderRelOffset + + ", " + + msg + " signature not found; was " + hexString); + } + + /** + * Closes this zip file. This method is idempotent. This method may cause I/O if the + * zip file needs to be deleted. + * + * @throws IOException + * if an IOException occurs. + */ + public void close() throws IOException { + // guard.close(); + RandomAccessFile localRaf = raf; + if (localRaf != null) { // Only close initialized instances + synchronized (localRaf) { + raf = null; + localRaf.close(); + } + if (fileToDeleteOnClose != null) { + fileToDeleteOnClose.delete(); + fileToDeleteOnClose = null; + } + } + } + + private void checkNotClosed() { + if (raf == null) { + throw new IllegalStateException("Zip file closed"); + } + } + + /** + * Returns an enumeration of the entries. The entries are listed in the + * order in which they appear in the zip file. + * + *

If you only need to iterate over the entries in a zip file, and don't + * need random-access entry lookup by name, you should probably use {@link ZipInputStream} + * instead, to avoid paying to construct the in-memory index. + * + * @throws IllegalStateException if this zip file has been closed. + */ + public Enumeration entries() { + checkNotClosed(); + final Iterator iterator = entries.values().iterator(); + return new Enumeration() { + public boolean hasMoreElements() { + checkNotClosed(); + return iterator.hasNext(); + } + public TinkerZipEntry nextElement() { + checkNotClosed(); + return iterator.next(); + } + }; + } + + /** + * Returns this file's comment, or null if it doesn't have one. + * See {@link ZipOutputStream#setComment}. + * + * @throws IllegalStateException if this zip file has been closed. + * @since 1.7 + */ + public String getComment() { + checkNotClosed(); + return comment; + } + + /** + * Returns the zip entry with the given name, or null if there is no such entry. + * + * @throws IllegalStateException if this zip file has been closed. + */ + public TinkerZipEntry getEntry(String entryName) { + checkNotClosed(); + if (entryName == null) { + throw new NullPointerException("entryName == null"); + } + TinkerZipEntry ze = entries.get(entryName); + if (ze == null) { + ze = entries.get(entryName + "/"); + } + return ze; + } + + /** + * Returns an input stream on the data of the specified {@code ZipEntry}. + * + * @param entry + * the ZipEntry. + * @return an input stream of the data contained in the {@code ZipEntry}. + * @throws IOException + * if an {@code IOException} occurs. + * @throws IllegalStateException if this zip file has been closed. + */ + public InputStream getInputStream(TinkerZipEntry entry) throws IOException { + // Make sure this ZipEntry is in this Zip file. We run it through the name lookup. + entry = getEntry(entry.getName()); + if (entry == null) { + return null; + } + // Create an InputStream at the right part of the file. + RandomAccessFile localRaf = raf; + synchronized (localRaf) { + // We don't know the entry data's start position. All we have is the + // position of the entry's local header. + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + RAFStream rafStream = new RAFStream(localRaf, entry.localHeaderRelOffset); + DataInputStream is = new DataInputStream(rafStream); + final int localMagic = Integer.reverseBytes(is.readInt()); + if (localMagic != LOCSIG) { + throwZipException(filename, localRaf.length(), entry.getName(), entry.localHeaderRelOffset, "Local File Header", localMagic); + } + is.skipBytes(2); + // At position 6 we find the General Purpose Bit Flag. + int gpbf = Short.reverseBytes(is.readShort()) & 0xffff; + if ((gpbf & TinkerZipFile.GPBF_UNSUPPORTED_MASK) != 0) { + throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf); + } + // Offset 26 has the file name length, and offset 28 has the extra field length. + // These lengths can differ from the ones in the central header. + is.skipBytes(18); + int fileNameLength = Short.reverseBytes(is.readShort()) & 0xffff; + int extraFieldLength = Short.reverseBytes(is.readShort()) & 0xffff; + is.close(); + // Skip the variable-size file name and extra field data. + rafStream.skip(fileNameLength + extraFieldLength); + /*if (entry.compressionMethod == ZipEntry.STORED) { + rafStream.endOffset = rafStream.offset + entry.size; + return rafStream; + } else { + rafStream.endOffset = rafStream.offset + entry.compressedSize; + int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L)); + return new ZipInflaterInputStream(rafStream, new Inflater(true), bufSize, entry); + }*/ + if (entry.compressionMethod == TinkerZipEntry.STORED) { + rafStream.endOffset = rafStream.offset + entry.size; + } else { + rafStream.endOffset = rafStream.offset + entry.compressedSize; + } + return rafStream; + } + } + + /** + * Gets the file name of this {@code ZipFile}. + * + * @return the file name of this {@code ZipFile}. + */ + public String getName() { + return filename; + } + + /** + * Returns the number of {@code ZipEntries} in this {@code ZipFile}. + * + * @return the number of entries in this file. + * @throws IllegalStateException if this zip file has been closed. + */ + public int size() { + checkNotClosed(); + return entries.size(); + } + + /** + * Find the central directory and read the contents. + * + *

The central directory can be followed by a variable-length comment + * field, so we have to scan through it backwards. The comment is at + * most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + *

This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everybody handles + * it though, so we're in good company if this fails. + */ + private void readCentralDir() throws IOException { + // Scan back, looking for the End Of Central Directory field. If the zip file doesn't + // have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD + // on the first try. + // No need to synchronize raf here -- we only do this when we first open the zip file. + long scanOffset = raf.length() - ENDHDR; + if (scanOffset < 0) { + throw new ZipException("File too short to be a zip file: " + raf.length()); + } + + raf.seek(0); + final int headerMagic = Integer.reverseBytes(raf.readInt()); + if (headerMagic != LOCSIG) { + throw new ZipException("Not a zip archive"); + } + + long stopOffset = scanOffset - 65536; + if (stopOffset < 0) { + stopOffset = 0; + } + + while (true) { + raf.seek(scanOffset); + if (Integer.reverseBytes(raf.readInt()) == ENDSIG) { + break; + } + + scanOffset--; + if (scanOffset < stopOffset) { + throw new ZipException("End Of Central Directory signature not found"); + } + } + + // Read the End Of Central Directory. ENDHDR includes the signature bytes, + // which we've already read. + byte[] eocd = new byte[ENDHDR - 4]; + raf.readFully(eocd); + + // Pull out the information we need. + BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN); + int diskNumber = it.readShort() & 0xffff; + int diskWithCentralDir = it.readShort() & 0xffff; + int numEntries = it.readShort() & 0xffff; + int totalNumEntries = it.readShort() & 0xffff; + it.skip(4); // Ignore centralDirSize. + long centralDirOffset = ((long) it.readInt()) & 0xffffffffL; + int commentLength = it.readShort() & 0xffff; + + if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) { + throw new ZipException("Spanned archives not supported"); + } + + if (commentLength > 0) { + byte[] commentBytes = new byte[commentLength]; + raf.readFully(commentBytes); + comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8); + } + + // Seek to the first CDE and read all entries. + // We have to do this now (from the constructor) rather than lazily because the + // public API doesn't allow us to throw IOException except from the constructor + // or from getInputStream. + RAFStream rafStream = new RAFStream(raf, centralDirOffset); + BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096); + byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry. + for (int i = 0; i < numEntries; ++i) { + TinkerZipEntry newEntry = new TinkerZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8, + (false) /* isZip64 */); + if (newEntry.localHeaderRelOffset >= centralDirOffset) { + throw new ZipException("Local file header offset is after central directory"); + } + String entryName = newEntry.getName(); + if (entries.put(entryName, newEntry) != null) { + throw new ZipException("Duplicate entry name: " + entryName); + } + } + + } + + // private final CloseGuard guard = CloseGuard.get(); + static class EocdRecord { + final long numEntries; + final long centralDirOffset; + final int commentLength; + EocdRecord(long numEntries, long centralDirOffset, int commentLength) { + this.numEntries = numEntries; + this.centralDirOffset = centralDirOffset; + this.commentLength = commentLength; + } + } + + /** + * Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared + * among all streams returned by getInputStream(), so we have to synchronize + * access to it. (We can optimize this by adding buffering here to reduce + * collisions.) + * + *

We could support mark/reset, but we don't currently need them. + * + * @hide + */ + public static class RAFStream extends InputStream { + private final RandomAccessFile sharedRaf; + private long endOffset; + private long offset; + public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) { + sharedRaf = raf; + offset = initialOffset; + this.endOffset = endOffset; + } + public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException { + this(raf, initialOffset, raf.length()); + } + @Override public int available() throws IOException { + return (offset < endOffset ? 1 : 0); + } + @Override public int read() throws IOException { + return Streams.readSingleByte(this); + } + @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { + synchronized (sharedRaf) { + final long length = endOffset - offset; + if (byteCount > length) { + byteCount = (int) length; + } + sharedRaf.seek(offset); + int count = sharedRaf.read(buffer, byteOffset, byteCount); + if (count > 0) { + offset += count; + return count; + } else { + return -1; + } + } + } + @Override public long skip(long byteCount) throws IOException { + if (byteCount > endOffset - offset) { + byteCount = endOffset - offset; + } + offset += byteCount; + return byteCount; + } + /*public int fill(Inflater inflater, int nativeEndBufSize) throws IOException { + synchronized (sharedRaf) { + int len = Math.min((int) (endOffset - offset), nativeEndBufSize); + int cnt = inflater.setFileInput(sharedRaf.getFD(), offset, nativeEndBufSize); + // setFileInput read from the file, so we need to get the OS and RAFStream back + // in sync... + skip(cnt); + return len; + } + }*/ + } + /** @hide */ + /*public static class ZipInflaterInputStream extends InflaterInputStream { + private final ZipEntry entry; + private long bytesRead = 0; + public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) { + super(is, inf, bsize); + this.entry = entry; + } + @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { + final int i; + try { + i = super.read(buffer, byteOffset, byteCount); + } catch (IOException e) { + throw new IOException("Error reading data for " + entry.getName() + " near offset " + + bytesRead, e); + } + if (i == -1) { + if (entry.size != bytesRead) { + throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs " + + entry.size); + } + } else { + bytesRead += i; + } + return i; + } + @Override public int available() throws IOException { + if (closed) { + // Our superclass will throw an exception, but there's a jtreg test that + // explicitly checks that the InputStream returned from ZipFile.getInputStream + // returns 0 even when closed. + return 0; + } + return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead); + } + }*/ +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipOutputStream.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipOutputStream.java new file mode 100644 index 00000000..62aabf47 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/TinkerZipOutputStream.java @@ -0,0 +1,579 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +// import libcore.util.CountingOutputStream; +// import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; + +// import java.nio.charset.StandardCharsets; +// import java.util.Arrays; + +/** + * modify by zhangshaowen on 16/6/7. + * remove zip64 + * const time, modDate + * remove entry extra + * remove entry comment + * + * Used to write (compress) data into zip files. + * + *

{@code ZipOutputStream} is used to write {@link TinkerZipEntry}s to the underlying + * stream. Output from {@code ZipOutputStream} can be read using {@link TinkerZipFile} + * or {@link ZipInputStream}. + * + *

While {@code DeflaterOutputStream} can write compressed zip file + * entries, this extension can write uncompressed entries as well. + * Use {@link TinkerZipEntry#setMethod} or @link #setMethod with the {@link TinkerZipEntry#STORED} flag. + * + *

Example

+ *

Using {@code ZipOutputStream} is a little more complicated than {@link GZIPOutputStream} + * because zip files are containers that can contain multiple files. This code creates a zip + * file containing several files, similar to the {@code zip(1)} utility. + *

+ * OutputStream os = ...
+ * ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
+ * try {
+ *     for (int i = 0; i < fileCount; ++i) {
+ *         String filename = ...
+ *         byte[] bytes = ...
+ *         ZipEntry entry = new ZipEntry(filename);
+ *         zos.putNextEntry(entry);
+ *         zos.write(bytes);
+ *         zos.closeEntry();
+ *     }
+ * } finally {
+ *     zos.close();
+ * }
+ * 
+ */ +public class TinkerZipOutputStream extends FilterOutputStream implements ZipConstants { + /** + * Indicates deflated entries. + */ + public static final int DEFLATED = 8; + /** + * Indicates uncompressed entries. + */ + public static final int STORED = 0; + public static final byte[] BYTE = new byte[0]; + //zhangshaowen edit here, we just want the same time and modDate + //remove random fields + final static int TIME_CONST = 40691; + final static int MOD_DATE_CONST = 18698; + private static final int ZIP_VERSION_2_0 = 20; // Zip specification version 2.0. + private static final byte[] ZIP64_PLACEHOLDER_BYTES = + new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + private final HashSet entries = new HashSet(); + /** + * Whether we force all entries in this archive to have a zip64 extended info record. + * This of course implies that the {@code currentEntryNeedsZip64} and + * {@code archiveNeedsZip64EocdRecord} are always {@code true}. + */ + private final boolean forceZip64; + private byte[] commentBytes = BYTE; + private int defaultCompressionMethod = DEFLATED; + // private int compressionLevel = Deflater.DEFAULT_COMPRESSION; + private ByteArrayOutputStream cDir = new ByteArrayOutputStream(); + private TinkerZipEntry currentEntry; + // private final CRC32 crc = new CRC32(); + private long offset = 0; + /** The charset-encoded name for the current entry. */ + private byte[] nameBytes; + /** The charset-encoded comment for the current entry. */ + private byte[] entryCommentBytes; + /** + * Whether this zip file needs a Zip64 EOCD record / zip64 EOCD record locator. This + * will be true if we wrote an entry whose size or compressed size was too large for + * the standard zip format or if we exceeded the maximum number of entries allowed + * in the standard format. + */ + private boolean archiveNeedsZip64EocdRecord; + /** + * Whether the current entry being processed needs a zip64 extended info record. This + * will be true if the entry is too large for the standard zip format or if the offset + * to the start of the current entry header is greater than 0xFFFFFFFF. + */ + private boolean currentEntryNeedsZip64; + /** + * Constructs a new {@code ZipOutputStream} that writes a zip file to the given + * {@code OutputStream}. + * + *

UTF-8 will be used to encode the file comment, entry names and comments. + */ + public TinkerZipOutputStream(OutputStream os) { + this(os, false /* forceZip64 */); + } + /** + * @hide for testing only. + */ + public TinkerZipOutputStream(OutputStream os, boolean forceZip64) { + super(os); + this.forceZip64 = forceZip64; + } + + /** + * Sets the default compression method to be used when a {@code ZipEntry} doesn't + * explicitly specify a method. See {@link TinkerZipEntry#setMethod} for more details. + */ + /*public void setMethod(int method) { + if (method != STORED && method != DEFLATED) { + throw new IllegalArgumentException("Bad method: " + method); + } + defaultCompressionMethod = method; + }*/ + static long writeLongAsUint32(OutputStream os, long i) throws IOException { + // Write out the long value as an unsigned int + os.write((int) (i & 0xFF)); + os.write((int) (i >> 8) & 0xFF); + os.write((int) (i >> 16) & 0xFF); + os.write((int) (i >> 24) & 0xFF); + return i; + } + + static long writeLongAsUint64(OutputStream os, long i) throws IOException { + int i1 = (int) i; + os.write(i1 & 0xFF); + os.write((i1 >> 8) & 0xFF); + os.write((i1 >> 16) & 0xFF); + os.write((i1 >> 24) & 0xFF); + int i2 = (int) (i >> 32); + os.write(i2 & 0xFF); + os.write((i2 >> 8) & 0xFF); + os.write((i2 >> 16) & 0xFF); + os.write((i2 >> 24) & 0xFF); + return i; + } + + static int writeIntAsUint16(OutputStream os, int i) throws IOException { + os.write(i & 0xFF); + os.write((i >> 8) & 0xFF); + return i; + } + + /** + * Closes the current {@code ZipEntry}, if any, and the underlying output + * stream. If the stream is already closed this method does nothing. + * + * @throws IOException + * If an error occurs closing the stream. + */ + @Override + public void close() throws IOException { + // don't call super.close() because that calls finish() conditionally + if (out != null) { + finish(); + // def.end(); + out.close(); + out = null; + } + } + /*private void checkAndSetZip64Requirements(ZipEntry entry) { + final long totalBytesWritten = getBytesWritten(); + final long entriesWritten = entries.size(); + currentEntryNeedsZip64 = false; + if (forceZip64) { + currentEntryNeedsZip64 = true; + archiveNeedsZip64EocdRecord = true; + return; + } + // In this particular case, we'll write a zip64 eocd record locator and a zip64 eocd + // record but we won't actually need zip64 extended info records for any of the individual + // entries (unless they trigger the checks below). + if (entriesWritten == 64*1024 - 1) { + archiveNeedsZip64EocdRecord = true; + } + // Check whether we'll need to write out a zip64 extended info record in both the local file header + // and the central directory. In addition, we will need a zip64 eocd record locator + // and record to mark this archive as zip64. + // + // TODO: This is an imprecise check. When method != STORED it's possible that the compressed + // size will be (slightly) larger than the actual size. How can we improve this ? + if (totalBytesWritten > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || + (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE)) { + currentEntryNeedsZip64 = true; + archiveNeedsZip64EocdRecord = true; + } + }*/ + + /** + * Closes the current {@code ZipEntry}. Any entry terminal data is written + * to the underlying stream. + * + * @throws IOException + * If an error occurs closing the entry. + */ + public void closeEntry() throws IOException { + checkOpen(); + if (currentEntry == null) { + return; + } + /*if (currentEntry.getMethod() == DEFLATED) { + super.finish(); + } + // Verify values for STORED types + if (currentEntry.getMethod() == STORED) { + if (crc.getValue() != currentEntry.crc) { + throw new ZipException("CRC mismatch"); + } + if (currentEntry.size != crc.tbytes) { + throw new ZipException("Size mismatch"); + } + }*/ + long curOffset = LOCHDR; + // Write the DataDescriptor + if (currentEntry.getMethod() != STORED) { + curOffset += EXTHDR; + // Data descriptor signature and CRC are 4 bytes each for both zip and zip64. + writeLongAsUint32(out, EXTSIG); + /*writeLongAsUint32(out, currentEntry.crc = crc.getValue()); + currentEntry.compressedSize = def.getBytesWritten(); + currentEntry.size = def.getBytesRead();*/ + writeLongAsUint32(out, currentEntry.crc); + /*if (currentEntryNeedsZip64) { + // We need an additional 8 bytes to store 8 byte compressed / uncompressed + // sizes. + curOffset += 8; + writeLongAsUint64(out, currentEntry.compressedSize); + writeLongAsUint64(out, currentEntry.size); + } else { + writeLongAsUint32(out, currentEntry.compressedSize); + writeLongAsUint32(out, currentEntry.size); + }*/ + writeLongAsUint32(out, currentEntry.compressedSize); + writeLongAsUint32(out, currentEntry.size); + } + // Update the CentralDirectory + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + int flags = currentEntry.getMethod() == STORED ? 0 : TinkerZipFile.GPBF_DATA_DESCRIPTOR_FLAG; + // Since gingerbread, we always set the UTF-8 flag on individual files if appropriate. + // Some tools insist that the central directory have the UTF-8 flag. + // http://code.google.com/p/android/issues/detail?id=20214 + flags |= TinkerZipFile.GPBF_UTF8_FLAG; + writeLongAsUint32(cDir, CENSIG); + writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Version this file was made by. + writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract. + writeIntAsUint16(cDir, flags); + writeIntAsUint16(cDir, currentEntry.getMethod()); + writeIntAsUint16(cDir, currentEntry.time); + writeIntAsUint16(cDir, currentEntry.modDate); + // writeLongAsUint32(cDir, crc.getValue()); + writeLongAsUint32(cDir, currentEntry.crc); + if (currentEntry.getMethod() == DEFLATED) { + /*currentEntry.setCompressedSize(def.getBytesWritten()); + currentEntry.setSize(def.getBytesRead());*/ + curOffset += currentEntry.getCompressedSize(); + } else { + /*currentEntry.setCompressedSize(crc.tbytes); + currentEntry.setSize(crc.tbytes);*/ + curOffset += currentEntry.getSize(); + } + /*if (currentEntryNeedsZip64) { + // Refresh the extended info with the compressed size / size before + // writing it to the central directory. + Zip64.refreshZip64ExtendedInfo(currentEntry); + // NOTE: We would've written out the zip64 extended info locator to the entry + // extras while constructing the local file header. There's no need to do it again + // here. If we do, there will be a size mismatch since we're calculating offsets + // based on the *current* size of the extra data and not based on the size + // at the point of writing the LFH. + writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); + writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); + } else { + writeLongAsUint32(cDir, currentEntry.getCompressedSize()); + writeLongAsUint32(cDir, currentEntry.getSize()); + }*/ + writeLongAsUint32(cDir, currentEntry.getCompressedSize()); + writeLongAsUint32(cDir, currentEntry.getSize()); + curOffset += writeIntAsUint16(cDir, nameBytes.length); + if (currentEntry.extra != null) { + curOffset += writeIntAsUint16(cDir, currentEntry.extra.length); + } else { + writeIntAsUint16(cDir, 0); + } + writeIntAsUint16(cDir, entryCommentBytes.length); // Comment length. + writeIntAsUint16(cDir, 0); // Disk Start + writeIntAsUint16(cDir, 0); // Internal File Attributes + writeLongAsUint32(cDir, 0); // External File Attributes + /*if (currentEntryNeedsZip64) { + writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); + } else { + writeLongAsUint32(cDir, currentEntry.localHeaderRelOffset); + }*/ + writeLongAsUint32(cDir, currentEntry.localHeaderRelOffset); + cDir.write(nameBytes); + nameBytes = null; + if (currentEntry.extra != null) { + cDir.write(currentEntry.extra); + } + offset += curOffset; + if (entryCommentBytes.length > 0) { + cDir.write(entryCommentBytes); + entryCommentBytes = BYTE; + } + currentEntry = null; + /*crc.reset(); + def.reset(); + done = false;*/ + } + /** + * Sets the compression level to be used + * for writing entry data. + */ + /*public void setLevel(int level) { + if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) { + throw new IllegalArgumentException("Bad level: " + level); + } + compressionLevel = level; + }*/ + + /** + * Indicates that all entries have been written to the stream. Any terminal + * information is written to the underlying stream. + * + * @throws IOException + * if an error occurs while terminating the stream. + */ + // @Override + public void finish() throws IOException { + // TODO: is there a bug here? why not checkOpen? + if (out == null) { + throw new IOException("Stream is closed"); + } + if (cDir == null) { + return; + } + if (entries.isEmpty()) { + throw new ZipException("No entries"); + } + if (currentEntry != null) { + closeEntry(); + } + int cdirEntriesSize = cDir.size(); + /*if (archiveNeedsZip64EocdRecord) { + Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize); + }*/ + // Write Central Dir End + writeLongAsUint32(cDir, ENDSIG); + writeIntAsUint16(cDir, 0); // Disk Number + writeIntAsUint16(cDir, 0); // Start Disk + // Instead of trying to figure out *why* this archive needed a zip64 eocd record, + // just delegate all these values to the zip64 eocd record. + if (archiveNeedsZip64EocdRecord) { + writeIntAsUint16(cDir, 0xFFFF); // Number of entries + writeIntAsUint16(cDir, 0xFFFF); // Number of entries + writeLongAsUint32(cDir, 0xFFFFFFFF); // Size of central dir + writeLongAsUint32(cDir, 0xFFFFFFFF); // Offset of central dir; + } else { + writeIntAsUint16(cDir, entries.size()); // Number of entries + writeIntAsUint16(cDir, entries.size()); // Number of entries + writeLongAsUint32(cDir, cdirEntriesSize); // Size of central dir + writeLongAsUint32(cDir, offset); // Offset of central dir + } + writeIntAsUint16(cDir, commentBytes.length); + if (commentBytes.length > 0) { + cDir.write(commentBytes); + } + // Write the central directory. + cDir.writeTo(out); + cDir = null; + } + + /** + * Writes entry information to the underlying stream. Data associated with + * the entry can then be written using {@code write()}. After data is + * written {@code closeEntry()} must be called to complete the writing of + * the entry to the underlying stream. + * + * @param ze + * the {@code ZipEntry} to store. + * @throws IOException + * If an error occurs storing the entry. + * @see #write + */ + public void putNextEntry(TinkerZipEntry ze) throws IOException { + if (currentEntry != null) { + closeEntry(); + } + // Did this ZipEntry specify a method, or should we use the default? + int method = ze.getMethod(); + if (method == -1) { + method = defaultCompressionMethod; + } + // If the method is STORED, check that the ZipEntry was configured appropriately. + if (method == STORED) { + if (ze.getCompressedSize() == -1) { + ze.setCompressedSize(ze.getSize()); + } else if (ze.getSize() == -1) { + ze.setSize(ze.getCompressedSize()); + } + if (ze.getCrc() == -1) { + throw new ZipException("STORED entry missing CRC"); + } + if (ze.getSize() == -1) { + throw new ZipException("STORED entry missing size"); + } + if (ze.size != ze.compressedSize) { + throw new ZipException("STORED entry size/compressed size mismatch"); + } + } + checkOpen(); + // checkAndSetZip64Requirements(ze); + + //zhangshaowen edit here, we just want the same time and modDate + ze.comment = null; + ze.extra = null; + ze.time = TIME_CONST; + ze.modDate = MOD_DATE_CONST; + + nameBytes = ze.name.getBytes(StandardCharsets.UTF_8); + checkSizeIsWithinShort("Name", nameBytes); + entryCommentBytes = BYTE; + if (ze.comment != null) { + entryCommentBytes = ze.comment.getBytes(StandardCharsets.UTF_8); + // The comment is not written out until the entry is finished, but it is validated here + // to fail-fast. + checkSizeIsWithinShort("Comment", entryCommentBytes); + } + // def.setLevel(compressionLevel); + ze.setMethod(method); + currentEntry = ze; + + currentEntry.localHeaderRelOffset = offset; + entries.add(currentEntry.name); + // Local file header. + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + int flags = (method == STORED) ? 0 : TinkerZipFile.GPBF_DATA_DESCRIPTOR_FLAG; + // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used + // modified UTF-8. From Java 7, when using UTF_8 it sets this flag and uses normal UTF-8.) + flags |= TinkerZipFile.GPBF_UTF8_FLAG; + writeLongAsUint32(out, LOCSIG); // Entry header + writeIntAsUint16(out, ZIP_VERSION_2_0); // Minimum version needed to extract. + writeIntAsUint16(out, flags); + writeIntAsUint16(out, method); + + //zhangshaowen edit here, we just want the same time and modDate +// if (currentEntry.getTime() == -1) { +// currentEntry.setTime(System.currentTimeMillis()); +// } + writeIntAsUint16(out, currentEntry.time); + writeIntAsUint16(out, currentEntry.modDate); + if (method == STORED) { + writeLongAsUint32(out, currentEntry.crc); + /*if (currentEntryNeedsZip64) { + // NOTE: According to the spec, we're allowed to use these fields under zip64 + // as long as the sizes are <= 4G (and omit writing the zip64 extended information header). + // + // For simplicity, we write the zip64 extended info here even if we only need it + // in the central directory (i.e, the case where we're turning on zip64 because the + // offset to this entries LFH is > 0xFFFFFFFF). + out.write(ZIP64_PLACEHOLDER_BYTES); // compressed size + out.write(ZIP64_PLACEHOLDER_BYTES); // uncompressed size + } else { + writeLongAsUint32(out, currentEntry.size); + writeLongAsUint32(out, currentEntry.size); + }*/ + writeLongAsUint32(out, currentEntry.size); + writeLongAsUint32(out, currentEntry.size); + } else { + writeLongAsUint32(out, 0); + writeLongAsUint32(out, 0); + writeLongAsUint32(out, 0); + } + writeIntAsUint16(out, nameBytes.length); + /*if (currentEntryNeedsZip64) { + Zip64.insertZip64ExtendedInfoToExtras(currentEntry); + }*/ + if (currentEntry.extra != null) { + writeIntAsUint16(out, currentEntry.extra.length); + } else { + writeIntAsUint16(out, 0); + } + out.write(nameBytes); + if (currentEntry.extra != null) { + out.write(currentEntry.extra); + } + } + + /** + * Sets the comment associated with the file being written. See {@link TinkerZipFile#getComment}. + * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes. + */ + public void setComment(String comment) { + if (comment == null) { + this.commentBytes = BYTE; + return; + } + byte[] newCommentBytes = comment.getBytes(StandardCharsets.UTF_8); + checkSizeIsWithinShort("Comment", newCommentBytes); + this.commentBytes = newCommentBytes; + } + + /** + * Writes data for the current entry to the underlying stream. + * + * @throws IOException + * If an error occurs writing to the stream + */ + @Override + public void write(byte[] buffer, int offset, int byteCount) throws IOException { + Arrays.checkOffsetAndCount(buffer.length, offset, byteCount); + if (currentEntry == null) { + throw new ZipException("No active entry"); + } + /*final long totalBytes = crc.tbytes + byteCount; + if ((totalBytes > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) && !currentEntryNeedsZip64) { + throw new IOException("Zip entry size (" + totalBytes + + " bytes) cannot be represented in the zip format (needs Zip64)." + + " Set the entry length using ZipEntry#setLength to use Zip64 where necessary."); + }*/ + if (currentEntry.getMethod() == STORED) { + out.write(buffer, offset, byteCount); + } else { + out.write(buffer, offset, byteCount); + } + // crc.update(buffer, offset, byteCount); + } + private void checkOpen() throws IOException { + if (cDir == null) { + throw new IOException("Stream is closed"); + } + } + private void checkSizeIsWithinShort(String property, byte[] bytes) { + if (bytes.length > 0xffff) { + throw new IllegalArgumentException(property + + " too long in UTF-8:" + + bytes.length + + " bytes"); + } + } + /*private long getBytesWritten() { + // This cast is somewhat messy but less error prone than keeping an + // CountingOutputStream reference around in addition to the FilterOutputStream's + // out. + return ((CountingOutputStream) out).getCount(); + }*/ +} diff --git a/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/ZipConstants.java b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/ZipConstants.java new file mode 100644 index 00000000..2bb0e394 --- /dev/null +++ b/tinker-commons/src/main/java/com/tencent/tinker/commons/ziputil/ZipConstants.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tencent.tinker.commons.ziputil; + +/** + * modify by zhangshaowen on 16/6/7. + * + * Do not add constants to this interface! It's implemented by the classes + * in this package whose names start "Zip", and the constants are thereby + * public API. + */ +interface ZipConstants { + long LOCSIG = 0x4034b50, EXTSIG = 0x8074b50, + CENSIG = 0x2014b50, ENDSIG = 0x6054b50; + int LOCHDR = 30, EXTHDR = 16, CENHDR = 46, ENDHDR = 22, + LOCVER = 4, LOCFLG = 6, LOCHOW = 8, LOCTIM = 10, LOCCRC = 14, + LOCSIZ = 18, LOCLEN = 22, LOCNAM = 26, LOCEXT = 28, EXTCRC = 4, + EXTSIZ = 8, EXTLEN = 12, CENVEM = 4, CENVER = 6, CENFLG = 8, + CENHOW = 10, CENTIM = 12, CENCRC = 16, CENSIZ = 20, CENLEN = 24, + CENNAM = 28, CENEXT = 30, CENCOM = 32, CENDSK = 34, CENATT = 36, + CENATX = 38, CENOFF = 42, ENDSUB = 8, ENDTOT = 10, ENDSIZ = 12, + ENDOFF = 16, ENDCOM = 20; +} diff --git a/tinker-sample-android/.gitignore b/tinker-sample-android/.gitignore new file mode 100644 index 00000000..c6cbe562 --- /dev/null +++ b/tinker-sample-android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/tinker-sample-android/app/.gitignore b/tinker-sample-android/app/.gitignore new file mode 100644 index 00000000..c125ebd4 --- /dev/null +++ b/tinker-sample-android/app/.gitignore @@ -0,0 +1,3 @@ +/build + +/version.properties diff --git a/tinker-sample-android/app/build.gradle b/tinker-sample-android/app/build.gradle new file mode 100644 index 00000000..39c944dd --- /dev/null +++ b/tinker-sample-android/app/build.gradle @@ -0,0 +1,354 @@ +apply plugin: 'com.android.application' + + + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile "com.android.support:appcompat-v7:23.1.1" + compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") + compile("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") + compile "com.android.support:multidex:1.0.1" + + //use to test multiDex +// compile group: 'com.google.guava', name: 'guava', version: '19.0' +// compile "org.scala-lang:scala-library:2.11.7" +} + +def gitSha() { + return 'git rev-parse --short HEAD'.execute().text.trim() +} + +def javaVersion = JavaVersion.VERSION_1_7 + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + compileOptions { + sourceCompatibility javaVersion + targetCompatibility javaVersion + } + //recommend + dexOptions { + jumboMode = true + } + + signingConfigs { + release { + try { + storeFile file("./keystore/release.keystore") + storePassword "testres" + keyAlias "testres" + keyPassword "testres" + } catch (ex) { + throw new InvalidUserDataException(ex.toString()) + } + } + + debug { + storeFile file("./keystore/debug.keystore") + } + } + + defaultConfig { + applicationId "tinker.sample.android" + minSdkVersion 10 + targetSdkVersion 22 + versionCode 1 + versionName "1.0.0" + /** + * you can use multiDex and install it in your ApplicationLifeCycle implement + */ + multiDexEnabled true + /** + * not like proguard, multiDexKeepProguard is not a list, so we can't just + * add for you in our task. you can copy tinker keep rules at + * build/intermediates/tinker_intermediates/tinker_multidexkeep.pro + */ + multiDexKeepProguard file("keep_in_main_dex.txt") + /** + * buildConfig can change during patch! + * we can use the newly value when patch + */ + buildConfigField "String", "MESSAGE", "\"I am the base apk\"" +// buildConfigField "String", "MESSAGE", "\"I am the patch apk\"" + /** + * client version would update with patch + * so we can get the newly git version easily! + */ + buildConfigField "String", "CLIENTVERSION", "\"${gitSha()}\"" + buildConfigField "String", "PLATFORM", "\"all\"" + } +// //use to test flavors support +// productFlavors { +// flavor1 { +// applicationId 'tinker.sample.android.flavor1' +// } +// +// flavor2 { +// applicationId 'tinker.sample.android.flavor2' +// } +// } + + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + debuggable true + minifyEnabled false + signingConfig signingConfigs.debug + } + } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } + } +} + +def bakPath = file("${buildDir}/bakApk/") + +/** + * you can use assembleRelease to build you base apk + * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch + * add apk from the build/bakApk + */ +ext { + //for some reason, you may want to ignore tinkerBuild, such as instant run debug build? + tinkerEnabled = true + //you should bak the following files + //old apk file to build patch apk + tinkerOldApkPath = "${bakPath}/app-debug-0919-20-32-57.apk" + //proguard mapping file to build patch apk + tinkerApplyMappingPath = "${bakPath}/" + //resource R.txt to build patch apk, must input if there is resource changed + tinkerApplyResourcePath = "${bakPath}/" +} + + +def getOldApkPath() { + return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath +} + +def getApplyMappingPath() { + return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath +} + +def getApplyResourceMappingPath() { + return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath +} + +def getTinkerIdValue() { + return hasProperty("TINKER_ID") ? TINKER_ID : gitSha() +} + +def buildWithTinker() { + return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled +} + +if (buildWithTinker()) { + apply plugin: 'com.tencent.tinker.patch' + + tinkerPatch { + /** + * necessary,default 'null' + * the old apk path, use to diff with the new apk to build + * add apk from the build/bakApk + */ + oldApk = getOldApkPath() + /** + * optional,default 'false' + * there are some cases we may get some warnings + * if ignoreWarning is true, we would just assert the patch process + * case 1: minSdkVersion is below 14, but you are using dexMode with raw. + * it must be crash when load. + * case 2: newly added Android Component in AndroidManifest.xml, + * it must be crash when load. + * case 3: loader classes in dex.loader{} are not keep in the main dex, + * it must be let tinker not work. + * case 4: loader classes in dex.loader{} changes, + * loader classes is ues to load patch dex. it is useless to change them. + * it won't crash, but these changes can't effect. you may ignore it + * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build + */ + ignoreWarning = false + /** + * optional,default 'true' + * whether sign the patch file + * if not, you must do yourself. otherwise it can't check success during the patch loading + * we will use the sign config with your build type + */ + useSign = true + + /** + * Warning, applyMapping will affect the normal android build! + */ + buildConfig { + /** + * optional,default 'null' + * if we use tinkerPatch to build the patch apk, you'd better to apply the old + * apk mapping file if minifyEnabled is enable! + * Warning: + * you must be careful that it will affect the normal assemble build! + */ + applyMapping = getApplyMappingPath() + /** + * optional,default 'null' + * It is nice to keep the resource id from R.txt file to reduce java changes + */ + applyResourceMapping = getApplyResourceMappingPath() + + /** + * necessary,default 'null' + * because we don't want to check the base apk with md5 in the runtime(it is slow) + * tinkerId is use to identify the unique base apk when the patch is tried to apply. + * we can use git rev, svn rev or simply versionCode. + * we will gen the tinkerId in your manifest automatic + */ + tinkerId = getTinkerIdValue() + } + + dex { + /** + * optional,default 'jar' + * only can be 'raw' or 'jar'. for raw, we would keep its original format + * for jar, we would repack dexes with zip format. + * if you want to support below 14, you must use jar + * or you want to save rom or check quicker, you can use raw mode also + */ + dexMode = "jar" + /** + * necessary,default '[]' + * what dexes in apk are expected to deal with tinkerPatch + * it support * or ? pattern. + */ + pattern = ["classes*.dex", + "assets/secondary-dex-?.jar"] + /** + * necessary,default '[]' + * Warning, it is very very important, loader classes can't change with patch. + * thus, they will be removed from patch dexes. + * you must put the following class into main dex. + * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} + * own tinkerLoader, and the classes you use in them + * + */ + loader = ["com.tencent.tinker.loader.*", + "tinker.sample.android.SampleApplication", + //use sample, let BaseBuildInfo unchangeable with tinker + "tinker.sample.android.app.BaseBuildInfo" + ] + } + + lib { + /** + * optional,default '[]' + * what library in apk are expected to deal with tinkerPatch + * it support * or ? pattern. + * for library in assets, we would just recover them in the patch directory + * you can get them in TinkerLoadResult with Tinker + */ + pattern = ["lib/armeabi/*.so"] + } + + res { + /** + * optional,default '[]' + * what resource in apk are expected to deal with tinkerPatch + * it support * or ? pattern. + * you must include all your resources in apk here, + * otherwise, they won't repack in the new apk resources. + */ + pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] + + /** + * optional,default '[]' + * the resource file exclude patterns, ignore add, delete or modify resource change + * it support * or ? pattern. + * Warning, we can only use for files no relative with resources.arsc + */ + ignoreChange = ["assets/sample_meta.txt"] + + /** + * default 100kb + * for modify resource, if it is larger than 'largeModSize' + * we would like to use bsdiff algorithm to reduce patch file size + */ + largeModSize = 100 + } + + packageConfig { + /** + * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' + * package meta file gen. path is assets/package_meta.txt in patch file + * you can use securityCheck.getPackageProperties() in your ownPackageCheck method + * or TinkerLoadResult.getPackageConfigByName + * we will get the TINKER_ID from the old apk manifest for you automatic, + * other config files (such as patchMessage below)is not necessary + */ + configField("patchMessage", "tinker is sample to use") + /** + * just a sample case, you can use such as sdkVersion, brand, channel... + * you can parse it in the SamplePatchListener. + * Then you can use patch conditional! + */ + configField("platform", "all") + + } + //or you can add config filed outside, or get meta value from old apk + //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test")) + //project.tinkerPatch.packageConfig.configField("test2", "sample") + + /** + * if you don't use zipArtifact or path, we just use 7za to try + */ + sevenZip { + /** + * optional,default '7za' + * the 7zip artifact path, it will use the right 7za with your platform + */ + zipArtifact = "com.tencent.tinker:seven-zip:1.0.0" + /** + * optional,default '7za' + * you can specify the 7za path yourself, it will overwrite the zipArtifact value + */ +// path = "/usr/local/bin/7za" + } + } + +/** + * task type, you want to bak + */ + def taskName = "debug" +/** + * bak apk and mapping + */ + tasks.getByName("assemble${taskName.capitalize()}") { + it.doLast { + copy { + def date = new Date().format("MMdd-HH-mm-ss") + from "${buildDir}/outputs/apk/${project.getName()}-${taskName}.apk" + into bakPath + rename { String fileName -> + fileName.replace("${project.getName()}-${taskName}.apk", "${project.getName()}-${taskName}-${date}.apk") + } + + from "${buildDir}/outputs/mapping/${taskName}/mapping.txt" + into bakPath + rename { String fileName -> + fileName.replace("mapping.txt", "${project.getName()}-${taskName}-${date}-mapping.txt") + } + + from "${buildDir}/intermediates/symbols/${taskName}/R.txt" + into bakPath + rename { String fileName -> + fileName.replace("R.txt", "${project.getName()}-${taskName}-${date}-R.txt") + } + } + } + } +} diff --git a/tinker-sample-android/app/keep_in_main_dex.txt b/tinker-sample-android/app/keep_in_main_dex.txt new file mode 100644 index 00000000..12c1dcf5 --- /dev/null +++ b/tinker-sample-android/app/keep_in_main_dex.txt @@ -0,0 +1,26 @@ +# you can copy the tinker keep rule at +# build/intermediates/tinker_intermediates/tinker_multidexkeep.pro + +-keep class com.tencent.tinker.loader.** { + *; +} + +-keep class tinker.sample.android.app.SampleApplication { + *; +} + +-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle { + *; +} + +-keep public class * extends com.tencent.tinker.loader.TinkerLoader { + *; +} + +-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication { + *; +} + +# here, it is your own keep rules. +# you must be careful that the class name you write won't be proguard +# but the tinker class above is OK, we have already keep for you! diff --git a/tinker-sample-android/app/keystore/debug.keystore b/tinker-sample-android/app/keystore/debug.keystore new file mode 100644 index 00000000..76385213 Binary files /dev/null and b/tinker-sample-android/app/keystore/debug.keystore differ diff --git a/tinker-sample-android/app/keystore/release.keystore b/tinker-sample-android/app/keystore/release.keystore new file mode 100644 index 00000000..c32e009e Binary files /dev/null and b/tinker-sample-android/app/keystore/release.keystore differ diff --git a/tinker-sample-android/app/libs/armeabi/libstlport_shared.so b/tinker-sample-android/app/libs/armeabi/libstlport_shared.so new file mode 100755 index 00000000..050818bb Binary files /dev/null and b/tinker-sample-android/app/libs/armeabi/libstlport_shared.so differ diff --git a/tinker-sample-android/app/proguard-rules.pro b/tinker-sample-android/app/proguard-rules.pro new file mode 100644 index 00000000..36186f49 --- /dev/null +++ b/tinker-sample-android/app/proguard-rules.pro @@ -0,0 +1,19 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-keepattributes SourceFile,LineNumberTable + diff --git a/tinker-sample-android/app/src/androidTest/java/tinker/sample/android/ApplicationTest.java b/tinker-sample-android/app/src/androidTest/java/tinker/sample/android/ApplicationTest.java new file mode 100644 index 00000000..7ef9c04b --- /dev/null +++ b/tinker-sample-android/app/src/androidTest/java/tinker/sample/android/ApplicationTest.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/tinker-sample-android/app/src/main/AndroidManifest.xml b/tinker-sample-android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9676eb94 --- /dev/null +++ b/tinker-sample-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/Log/MyLogImp.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/Log/MyLogImp.java new file mode 100644 index 00000000..429accb9 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/Log/MyLogImp.java @@ -0,0 +1,96 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.Log; + +import android.util.Log; + +import com.tencent.tinker.lib.util.TinkerLog; + +/** + * Created by zhangshaowen on 16/6/3. + */ +public class MyLogImp implements TinkerLog.TinkerLogImp { + private static final String TAG = "Tinker.MyLogImp"; + + public static final int LEVEL_VERBOSE = 0; + public static final int LEVEL_DEBUG = 1; + public static final int LEVEL_INFO = 2; + public static final int LEVEL_WARNING = 3; + public static final int LEVEL_ERROR = 4; + public static final int LEVEL_NONE = 5; + private static int level = LEVEL_VERBOSE; + + public static int getLogLevel() { + return level; + } + + public static void setLevel(final int level) { + MyLogImp.level = level; + android.util.Log.w(TAG, "new log level: " + level); + + } + + @Override + public void v(String s, String s1, Object... objects) { + if (level <= LEVEL_VERBOSE) { + final String log = objects == null ? s1 : String.format(s1, objects); + android.util.Log.v(s, log); + } + } + + @Override + public void i(String s, String s1, Object... objects) { + if (level <= LEVEL_INFO) { + final String log = objects == null ? s1 : String.format(s1, objects); + android.util.Log.i(s, log); + } + } + + @Override + public void w(String s, String s1, Object... objects) { + if (level <= LEVEL_WARNING) { + final String log = objects == null ? s1 : String.format(s1, objects); + android.util.Log.w(s, log); + } + } + + @Override + public void d(String s, String s1, Object... objects) { + if (level <= LEVEL_DEBUG) { + final String log = objects == null ? s1 : String.format(s1, objects); + android.util.Log.d(s, log); + } + } + + @Override + public void e(String s, String s1, Object... objects) { + if (level <= LEVEL_ERROR) { + final String log = objects == null ? s1 : String.format(s1, objects); + android.util.Log.e(s, log); + } + } + + @Override + public void printErrStackTrace(String s, Throwable throwable, String s1, Object... objects) { + String log = objects == null ? s1 : String.format(s1, objects); + if (log == null) { + log = ""; + } + log = log + " " + Log.getStackTraceString(throwable); + android.util.Log.e(s, log); + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BaseBuildInfo.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BaseBuildInfo.java new file mode 100644 index 00000000..4ffa7d4d --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BaseBuildInfo.java @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.app; + +/** + * Created by zhangshaowen on 16/6/30. + * we add BaseBuildInfo to loader pattern, so it won't change with patch! + */ +public class BaseBuildInfo { + public static String TEST_MESSAGE = "I won't change with tinker patch!"; +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BuildInfo.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BuildInfo.java new file mode 100644 index 00000000..58ea3527 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/BuildInfo.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.app; + +import tinker.sample.android.BuildConfig; + +/** + * Created by zhangshaowen on 16/6/30. + * we use BuildInfo instead of {@link BuildInfo} to make less change + */ +public class BuildInfo { + /** + * they are not final, so they won't change with the BuildConfig values! + */ + public static boolean DEBUG = BuildConfig.DEBUG; + public static String VERSION_NAME = BuildConfig.VERSION_NAME; + public static int VERSION_CODE = BuildConfig.VERSION_CODE; + + public static String MESSAGE = BuildConfig.MESSAGE; + public static String CLIENTVERSION = BuildConfig.CLIENTVERSION; + public static String PLATFORM = BuildConfig.PLATFORM; + +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/app/MainActivity.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/MainActivity.java new file mode 100644 index 00000000..bc55dc62 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/MainActivity.java @@ -0,0 +1,155 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.app; + +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Environment; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import tinker.sample.android.R; +import tinker.sample.android.util.Utils; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = "Tinker.MainActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Log.e(TAG, "i am on onCreate classloader:" + MainActivity.class.getClassLoader().toString()); + //test resource change + Log.e(TAG, "i am on onCreate string:" + getResources().getString(R.string.test_resource)); +// Log.e(TAG, "i am on patch onCreate"); + + Button loadPatchButton = (Button) findViewById(R.id.loadPatch); + + loadPatchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk"); + } + }); + + Button loadLibraryButton = (Button) findViewById(R.id.loadLibrary); + + loadLibraryButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //for lib/armeabi, just use TinkerInstaller.loadLibrary + TinkerInstaller.loadArmLibrary(getApplicationContext(), "stlport_shared"); +// TinkerInstaller.loadLibraryFromTinker(getApplicationContext(), "assets/x86", "stlport_shared"); + } + }); + + Button cleanPatchButton = (Button) findViewById(R.id.cleanPatch); + + cleanPatchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Tinker.with(getApplicationContext()).cleanPatch(); + } + }); + + Button killSelfButton = (Button) findViewById(R.id.killSelf); + + killSelfButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + android.os.Process.killProcess(android.os.Process.myPid()); + } + }); + + Button buildInfoButton = (Button) findViewById(R.id.showInfo); + + buildInfoButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showInfo(MainActivity.this); + } + }); + } + + public boolean showInfo(Context context) { + // add more Build Info + final StringBuilder sb = new StringBuilder(); + Tinker tinker = Tinker.with(getApplicationContext()); + if (tinker.isTinkerLoaded()) { + sb.append(String.format("[patch is loaded] \n")); + sb.append(String.format("[buildConfig CLIENTVERSION] %s \n", BuildInfo.CLIENTVERSION)); + sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE)); + sb.append(String.format("[TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName(ShareConstants.TINKER_ID))); + sb.append(String.format("[REAL TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getTinkerID())); + sb.append(String.format("[packageConfig patchMessage] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName("patchMessage"))); + sb.append(String.format("[TINKER_ID Rom Space] %d k \n", tinker.getTinkerRomSpace())); + + } else { + sb.append(String.format("[patch is not loaded] \n")); + sb.append(String.format("[buildConfig CLIENTVERSION] %s \n", BuildInfo.CLIENTVERSION)); + sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE)); + sb.append(String.format("[TINKER_ID] %s \n", ShareTinkerInternals.getManifestTinkerID(getApplicationContext()))); + } + sb.append(String.format("[BaseBuildInfo Message] %s \n", BaseBuildInfo.TEST_MESSAGE)); + + final TextView v = new TextView(context); + v.setText(sb); + v.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10); + v.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + v.setTextColor(0xFF000000); + v.setTypeface(Typeface.MONOSPACE); + final int padding = 16; + v.setPadding(padding, padding, padding, padding); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCancelable(true); + builder.setView(v); + final AlertDialog alert = builder.create(); + alert.show(); + return true; + } + + @Override + protected void onResume() { + Log.e(TAG, "i am on onResume"); +// Log.e(TAG, "i am on patch onResume"); + + super.onResume(); + Utils.setBackground(false); + + } + + @Override + protected void onPause() { + super.onPause(); + Utils.setBackground(true); + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java new file mode 100644 index 00000000..48ddaf4e --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.app; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Build; +import android.support.multidex.MultiDex; + +import com.tencent.tinker.anno.DefaultLifeCycle; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.loader.app.ApplicationLifeCycle; +import com.tencent.tinker.loader.app.DefaultApplicationLike; +import com.tencent.tinker.loader.shareutil.ShareConstants; + +import tinker.sample.android.Log.MyLogImp; +import tinker.sample.android.util.SampleApplicationContext; +import tinker.sample.android.util.TinkerManager; + +/** + * because you can not use any other class in your application, we need to + * move your implement of Application to {@link ApplicationLifeCycle} + * As Application, all its direct reference class should be in the main dex. + * + * We use tinker-android-anno to make sure all your classes can be patched. + * + * application: if it is start with '.', we will add SampleApplicationLifeCycle's package name + * + * flags: + * TINKER_ENABLE_ALL: support dex, lib and resource + * TINKER_DEX_MASK: just support dex + * TINKER_NATIVE_LIBRARY_MASK: just support lib + * TINKER_RESOURCE_MASK: just support resource + * + * loaderClass: define the tinker loader class, we can just use the default TinkerLoader + * + * loadVerifyFlag: whether check files' md5 on the load time, defualt it is false. + * + * Created by zhangshaowen on 16/3/17. + */ +@SuppressWarnings("unused") +@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication", + flags = ShareConstants.TINKER_ENABLE_ALL, + loadVerifyFlag = false) +public class SampleApplicationLike extends DefaultApplicationLike { + private static final String TAG = "Tinker.SampleApplicationLike"; + + public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, + long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, + Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) { + super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager); + } + + /** + * install multiDex before install tinker + * so we don't need to put the tinker lib classes in the main dex + * + * @param base + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onBaseContextAttached(Context base) { + super.onBaseContextAttached(base); + //you must install multiDex whatever tinker is installed! + MultiDex.install(base); + + SampleApplicationContext.application = getApplication(); + SampleApplicationContext.context = getApplication().getApplicationContext(); + TinkerManager.setTinkerApplicationLike(this); + TinkerManager.initFastCrashProtect(); + //should set before tinker is installed + TinkerManager.setUpgradeRetryEnable(true); + + //optional set logIml, or you can use default debug log + TinkerInstaller.setLogIml(new MyLogImp()); + + //installTinker after load multiDex + //or you can put com.tencent.tinker.** to main dex + TinkerManager.installTinker(this); + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) { + getApplication().registerActivityLifecycleCallbacks(callback); + } + +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/crash/SampleUncaughtExceptionHandler.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/crash/SampleUncaughtExceptionHandler.java new file mode 100644 index 00000000..b98bbb4d --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/crash/SampleUncaughtExceptionHandler.java @@ -0,0 +1,140 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.crash; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.widget.Toast; + +import com.tencent.tinker.lib.tinker.TinkerApplicationHelper; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.app.ApplicationLike; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import tinker.sample.android.reporter.SampleTinkerReport; +import tinker.sample.android.util.TinkerManager; +import tinker.sample.android.util.Utils; + +/** + * optional, use dynamic configuration is better way + * for native crash, + *

+ * Created by zhangshaowen on 16/7/3. + * tinker's crash is caught by {@code LoadReporter.onLoadException} + * use {@code TinkerApplicationHelper} api, no need to install tinker! + */ +public class SampleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + private static final String TAG = "Tinker.SampleUncaughtExHandler"; + + private final Thread.UncaughtExceptionHandler ueh; + private static final long QUICK_CRASH_ELAPSE = 10 * 1000; + public static final int MAX_CRASH_COUNT = 3; + private static final String DALVIK_XPOSED_CRASH = "Class ref in pre-verified class resolved to unexpected implementation"; + + public SampleUncaughtExceptionHandler() { + ueh = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage()); + tinkerFastCrashProtect(); + tinkerPreVerifiedCrashHandler(ex); + ueh.uncaughtException(thread, ex); + } + + /** + * Such as Xposed, if it try to load some class before we load from patch files. + * With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation". + * With art, it may crash at some times. But we can't know the actual crash type. + * If it use Xposed, we can just clean patch or mention user to uninstall it. + */ + private void tinkerPreVerifiedCrashHandler(Throwable ex) { + if (Utils.isXposedExists(ex)) { + //method 1 + ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike(); + if (applicationLike == null || applicationLike.getApplication() == null) { + return; + } + + if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { + return; + } + boolean isCausedByXposed = false; + //for art, we can't know the actually crash type + //art's xposed has not much people + if (ShareTinkerInternals.isVmArt()) { + isCausedByXposed = true; + } else if (ex instanceof IllegalAccessError && ex.getMessage().contains(DALVIK_XPOSED_CRASH)) { + //for dalvik, we know the actual crash type + isCausedByXposed = true; + } + + if (isCausedByXposed) { + SampleTinkerReport.onXposedCrash(); + TinkerLog.e(TAG, "have xposed: just clean tinker"); + //kill all other process to ensure that all process's code is the same. + ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication()); + + TinkerApplicationHelper.cleanPatch(applicationLike); + ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication()); + //method 2 + //or you can mention user to uninstall Xposed! + Toast.makeText(applicationLike.getApplication(), "please uninstall Xposed, illegal modify the app", Toast.LENGTH_LONG).show(); + } + } + } + + /** + * if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch. + */ + private boolean tinkerFastCrashProtect() { + ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike(); + + if (applicationLike == null || applicationLike.getApplication() == null) { + return false; + } + if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) { + return false; + } + + final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime(); + //this process may not install tinker, so we use TinkerApplicationHelper api + if (elapsedTime < QUICK_CRASH_ELAPSE) { + String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike); + if (ShareTinkerInternals.isNullOrNil(currentVersion)) { + return false; + } + + SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); + int fastCrashCount = sp.getInt(currentVersion, 0); + if (fastCrashCount >= MAX_CRASH_COUNT) { + SampleTinkerReport.onFastCrashProtect(); + TinkerApplicationHelper.cleanPatch(applicationLike); + TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount); + return true; + } else { + sp.edit().putInt(currentVersion, ++fastCrashCount).commit(); + TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount); + } + } + + return false; + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleLoadReporter.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleLoadReporter.java new file mode 100644 index 00000000..138f7725 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleLoadReporter.java @@ -0,0 +1,122 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.reporter; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; +import android.widget.Toast; + +import com.tencent.tinker.lib.reporter.DefaultLoadReporter; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.loader.shareutil.ShareConstants; + +import java.io.File; + +import tinker.sample.android.util.UpgradePatchRetry; +import tinker.sample.android.util.Utils; + +/** + * optional, you can just use DefaultLoadReporter + * Created by zhangshaowen on 16/4/13. + */ +public class SampleLoadReporter extends DefaultLoadReporter { + private Handler handler = new Handler(); + + public SampleLoadReporter(Context context) { + super(context); + } + + @Override + public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode, final boolean isUpgrade) { + super.onLoadPatchListenerReceiveFail(patchFile, errorCode, isUpgrade); + switch (errorCode) { + case ShareConstants.ERROR_PATCH_NOTEXIST: + Toast.makeText(context, "patch file is not exist", Toast.LENGTH_LONG).show(); + break; + case ShareConstants.ERROR_PATCH_RUNNING: + // try later + // only retry for upgrade patch + if (isUpgrade) { + handler.postDelayed(new Runnable() { + @Override + public void run() { + TinkerInstaller.onReceiveUpgradePatch(context, patchFile.getAbsolutePath()); + } + }, 60 * 1000); + } + break; + case Utils.ERROR_PATCH_ROM_SPACE: + Toast.makeText(context, "rom space is not enough", Toast.LENGTH_LONG).show(); + break; + } + SampleTinkerReport.onTryApplyFail(errorCode); + } + + @Override + public void onLoadResult(File patchDirectory, int loadCode, long cost) { + super.onLoadResult(patchDirectory, loadCode, cost); + switch (loadCode) { + case ShareConstants.ERROR_LOAD_OK: + SampleTinkerReport.onLoaded(cost); + break; + } + Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() { + @Override public boolean queueIdle() { + UpgradePatchRetry.getInstance(context).onPatchRetryLoad(); + return false; + } + }); + } + @Override + public void onLoadException(Throwable e, int errorCode) { + super.onLoadException(e, errorCode); + SampleTinkerReport.onLoadException(e, errorCode); + } + + @Override + public void onLoadFileMd5Mismatch(File file, int fileType) { + super.onLoadFileMd5Mismatch(file, fileType); + SampleTinkerReport.onLoadFileMisMatch(fileType); + } + + @Override + public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) { + super.onLoadFileNotFound(file, fileType, isDirectory); + SampleTinkerReport.onLoadFileNotFound(fileType); + } + + @Override + public void onLoadPackageCheckFail(File patchFile, int errorCode) { + super.onLoadPackageCheckFail(patchFile, errorCode); + SampleTinkerReport.onLoadPackageCheckFail(errorCode); + } + + @Override + public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) { + super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); + SampleTinkerReport.onLoadInfoCorrupted(); + } + + @Override + public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) { + super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName); + } + +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchListener.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchListener.java new file mode 100644 index 00000000..852ef447 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchListener.java @@ -0,0 +1,122 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.reporter; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.SharedPreferences; + +import com.tencent.tinker.lib.listener.DefaultPatchListener; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerLoadResult; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import java.io.File; +import java.util.Properties; + +import tinker.sample.android.app.BuildInfo; +import tinker.sample.android.crash.SampleUncaughtExceptionHandler; +import tinker.sample.android.util.Utils; + +/** + * Created by zhangshaowen on 16/4/30. + * optional, you can just use DefaultPatchListener + * we can check whatever you want whether we actually send a patch request + * such as we can check rom space or apk channel + */ +public class SamplePatchListener extends DefaultPatchListener { + private static final String TAG = "Tinker.SamplePatchListener"; + + protected static final long NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN = 60 * 1024 * 1024; + protected static final long OLD_PATCH_RESTRICTION_SPACE_SIZE_MIN = 30 * 1024 * 1024; + + private final int maxMemory; + + public SamplePatchListener(Context context) { + super(context); + maxMemory = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + TinkerLog.i(TAG, "application maxMemory:" + maxMemory); + } + + /** + * because we use the defaultCheckPatchReceived method + * the error code define by myself should after {@code ShareConstants.ERROR_RECOVER_INSERVICE + * + * @param path + * @param newPatch + * @return + */ + @Override + public int patchCheck(String path, boolean isUpgrade) { + File patchFile = new File(path); + TinkerLog.i(TAG, "receive a patch file: %s, isUpgrade:%b, file size:%d", path, isUpgrade, SharePatchFileUtil.getFileOrDirectorySize(patchFile)); + int returnCode = super.patchCheck(path, isUpgrade); + + if (returnCode == ShareConstants.ERROR_PATCH_OK) { + if (isUpgrade) { + returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory); + } else { + returnCode = Utils.checkForPatchRecover(OLD_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory); + } + } + + if (returnCode == ShareConstants.ERROR_PATCH_OK) { + String patchMd5 = SharePatchFileUtil.getMD5(patchFile); + SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); + //optional, only disable this patch file with md5 + int fastCrashCount = sp.getInt(patchMd5, 0); + if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) { + returnCode = Utils.ERROR_PATCH_CRASH_LIMIT; + } else { + //for upgrade patch, version must be not the same + //for repair patch, we won't has the tinker load flag + Tinker tinker = Tinker.with(context); + + if (tinker.isTinkerLoaded()) { + TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent(); + if (tinkerLoadResult != null) { + String currentVersion = tinkerLoadResult.currentVersion; + if (patchMd5.equals(currentVersion)) { + returnCode = Utils.ERROR_PATCH_ALREADY_APPLY; + } + } + } + } + } + // Warning, it is just a sample case, you don't need to copy all of these + // Interception some of the request + if (returnCode == ShareConstants.ERROR_PATCH_OK) { + Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile); + if (properties == null) { + returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED; + } else { + String platform = properties.getProperty(Utils.PLATFORM); + TinkerLog.i(TAG, "get platform:" + platform); + // check patch platform require + if (platform == null || !platform.equals(BuildInfo.PLATFORM)) { + returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED; + } + } + } + + SampleTinkerReport.onTryApply(isUpgrade, returnCode == ShareConstants.ERROR_PATCH_OK); + return returnCode; + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchReporter.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchReporter.java new file mode 100644 index 00000000..a3566d68 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SamplePatchReporter.java @@ -0,0 +1,87 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.reporter; + +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.reporter.DefaultPatchReporter; +import com.tencent.tinker.loader.shareutil.SharePatchInfo; + +import java.io.File; + +import tinker.sample.android.util.UpgradePatchRetry; + +/** + * optional, you can just use DefaultPatchReporter + * Created by zhangshaowen on 16/4/8. + */ +public class SamplePatchReporter extends DefaultPatchReporter { + public SamplePatchReporter(Context context) { + super(context); + } + + @Override + public void onPatchServiceStart(Intent intent) { + super.onPatchServiceStart(intent); + SampleTinkerReport.onApplyPatchServiceStart(); + UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent); + } + + @Override + public void onPatchDexOptFail(File patchFile, File dexFile, String optDirectory, String dexName, Throwable t, boolean isUpgradePatch) { + super.onPatchDexOptFail(patchFile, dexFile, optDirectory, dexName, t, isUpgradePatch); + SampleTinkerReport.onApplyDexOptFail(t); + } + + @Override + public void onPatchException(File patchFile, Throwable e, boolean isUpgradePatch) { + super.onPatchException(patchFile, e, isUpgradePatch); + SampleTinkerReport.onApplyCrash(e); + } + + @Override + public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion, boolean isUpgradePatch) { + super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion, isUpgradePatch); + SampleTinkerReport.onApplyInfoCorrupted(); + } + + @Override + public void onPatchPackageCheckFail(File patchFile, boolean isUpgradePatch, int errorCode) { + super.onPatchPackageCheckFail(patchFile, isUpgradePatch, errorCode); + SampleTinkerReport.onApplyPackageCheckFail(errorCode); + } + + @Override + public void onPatchResult(File patchFile, boolean success, long cost, boolean isUpgradePatch) { + super.onPatchResult(patchFile, success, cost, isUpgradePatch); + SampleTinkerReport.onApplied(isUpgradePatch, cost, success); + UpgradePatchRetry.getInstance(context).onPatchServiceResult(isUpgradePatch); + } + + @Override + public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType, boolean isUpgradePatch) { + super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType, isUpgradePatch); + SampleTinkerReport.onApplyExtractFail(fileType); + } + + @Override + public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion, boolean isUpgradePatch) { + super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion, isUpgradePatch); + SampleTinkerReport.onApplyVersionCheckFail(); + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleTinkerReport.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleTinkerReport.java new file mode 100644 index 00000000..269f1066 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/reporter/SampleTinkerReport.java @@ -0,0 +1,522 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.reporter; + +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.shareutil.ShareConstants; +import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; + +import tinker.sample.android.util.Utils; + +/** + * a simple tinker data reporter + * Created by zhangshaowen on 16/9/17. + */ +public class SampleTinkerReport { + private static final String TAG = "Tinker.SampleTinkerReport"; + + // KEY - PV + public static final int KEY_REQUEST = 0; + public static final int KEY_DOWNLOAD = 1; + public static final int KEY_TRY_APPLY = 2; + public static final int KEY_TRY_APPLY_SUCCESS = 3; + public static final int KEY_APPLIED_START = 4; + public static final int KEY_APPLIED = 5; + public static final int KEY_LOADED = 6; + public static final int KEY_CRASH_FAST_PROTECT = 7; + public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8; + public static final int KEY_CRASH_CAUSE_XPOSED_ART = 9; + public static final int KEY_APPLY_WITH_RETRY = 10; + + //Key -- try apply detail + public static final int KEY_TRY_APPLY_REPAIR = 70; + public static final int KEY_TRY_APPLY_UPGRADE = 71; + public static final int KEY_TRY_APPLY_DISABLE = 72; + public static final int KEY_TRY_APPLY_RUNNING = 73; + public static final int KEY_TRY_APPLY_INSERVICE = 74; + public static final int KEY_TRY_APPLY_NOT_EXIST = 75; + public static final int KEY_TRY_APPLY_GOOGLEPLAY = 76; + public static final int KEY_TRY_APPLY_ROM_SPACE = 77; + public static final int KEY_TRY_APPLY_ALREADY_APPLY = 78; + public static final int KEY_TRY_APPLY_MEMORY_LIMIT = 79; + public static final int KEY_TRY_APPLY_CRASH_LIMIT = 80; + public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 81; + + //Key -- apply detail + public static final int KEY_APPLIED_REPAIR = 100; + public static final int KEY_APPLIED_UPGRADE = 101; + public static final int KEY_APPLIED_REPAIR_FAIL = 102; + public static final int KEY_APPLIED_UPGRADE_FAIL = 103; + + public static final int KEY_APPLIED_EXCEPTION = 120; + public static final int KEY_APPLIED_DEXOPT = 121; + public static final int KEY_APPLIED_INFO_CORRUPTED = 122; + //package check + public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE = 150; + public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META = 151; + public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META = 152; + public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 153; + public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154; + public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND = 155; + public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 156; + public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META = 157; + //version check + public static final int KEY_APPLIED_VERSION_CHECK = 180; + //extract error + public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181; + public static final int KEY_APPLIED_DEX_EXTRACT = 182; + /** + * for art small dex + */ + public static final int KEY_APPLIED_DEX_ART_EXTRACT = 183; + public static final int KEY_APPLIED_LIB_EXTRACT = 184; + public static final int KEY_APPLIED_RESOURCE_EXTRACT = 185; + //cost time + public static final int KEY_APPLIED_SUCC_COST_5S_LESS = 200; + public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201; + public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202; + public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203; + public static final int KEY_APPLIED_SUCC_COST_OTHER = 204; + + public static final int KEY_APPLIED_FAIL_COST_5S_LESS = 205; + public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206; + public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207; + public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208; + public static final int KEY_APPLIED_FAIL_COST_OTHER = 209; + + + // KEY -- load detail + public static final int KEY_LOADED_UNKNOWN_EXCEPTION = 250; + public static final int KEY_LOADED_UNCAUGHT_EXCEPTION = 251; + public static final int KEY_LOADED_EXCEPTION_DEX = 252; + public static final int KEY_LOADED_EXCEPTION_DEX_CHECK = 253; + public static final int KEY_LOADED_EXCEPTION_RESOURCE = 254; + + public static final int KEY_LOADED_MISMATCH_DEX = 300; + public static final int KEY_LOADED_MISMATCH_LIB = 301; + public static final int KEY_LOADED_MISMATCH_RESOURCE = 302; + public static final int KEY_LOADED_MISSING_DEX = 303; + public static final int KEY_LOADED_MISSING_LIB = 304; + public static final int KEY_LOADED_MISSING_PATCH_FILE = 305; + public static final int KEY_LOADED_MISSING_PATCH_INFO = 306; + public static final int KEY_LOADED_MISSING_DEX_OPT = 307; + public static final int KEY_LOADED_MISSING_RES = 308; + public static final int KEY_LOADED_INFO_CORRUPTED = 309; + + //load package check + public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE = 350; + public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META = 351; + public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META = 352; + public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = 353; + public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354; + public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = 355; + public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = 356; + public static final int KEY_LOADED_PACKAGE_CHECK_RES_META = 357; + + public static final int KEY_LOADED_SUCC_COST_500_LESS = 400; + public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401; + public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402; + public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403; + public static final int KEY_LOADED_SUCC_COST_OTHER = 404; + + interface Reporter { + void onReport(int key); + + void onReport(String message); + } + + private static Reporter reporter = null; + + public void setReporter(Reporter reporter) { + this.reporter = reporter; + } + + public static void onTryApply(boolean upgrade, boolean success) { + if (reporter == null) { + return; + } + reporter.onReport(KEY_TRY_APPLY); + if (upgrade) { + reporter.onReport(KEY_TRY_APPLY_UPGRADE); + } else { + reporter.onReport(KEY_TRY_APPLY_REPAIR); + } + if (success) { + reporter.onReport(KEY_TRY_APPLY_SUCCESS); + } + } + + public static void onTryApplyFail(int errorCode) { + if (reporter == null) { + return; + } + switch (errorCode) { + case ShareConstants.ERROR_PATCH_NOTEXIST: + reporter.onReport(KEY_TRY_APPLY_NOT_EXIST); + break; + case ShareConstants.ERROR_PATCH_DISABLE: + reporter.onReport(KEY_TRY_APPLY_DISABLE); + break; + case ShareConstants.ERROR_PATCH_INSERVICE: + reporter.onReport(KEY_TRY_APPLY_INSERVICE); + break; + case ShareConstants.ERROR_PATCH_RUNNING: + reporter.onReport(KEY_TRY_APPLY_RUNNING); + break; + case Utils.ERROR_PATCH_ROM_SPACE: + reporter.onReport(KEY_TRY_APPLY_ROM_SPACE); + break; + case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL: + reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY); + break; + case Utils.ERROR_PATCH_ALREADY_APPLY: + reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY); + break; + case Utils.ERROR_PATCH_CRASH_LIMIT: + reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT); + break; + case Utils.ERROR_PATCH_MEMORY_LIMIT: + reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT); + break; + case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED: + reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED); + break; + } + } + + public static void onLoadPackageCheckFail(int errorCode) { + if (reporter == null) { + return; + } + switch (errorCode) { + case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL); + + break; + case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED: + reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META); + break; + } + } + + public static void onLoaded(long cost) { + if (reporter == null) { + return; + } + reporter.onReport(KEY_LOADED); + + if (cost < 0L) { + TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost"); + return; + } + + if (cost <= 500) { + reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS); + } else if (cost <= 1000) { + reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS); + } else if (cost <= 3000) { + reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS); + } else if (cost <= 5000) { + reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS); + } else { + reporter.onReport(KEY_LOADED_SUCC_COST_OTHER); + } + } + + public static void onLoadInfoCorrupted() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_LOADED_INFO_CORRUPTED); + } + + public static void onLoadFileNotFound(int fileType) { + if (reporter == null) { + return; + } + switch (fileType) { + case ShareConstants.TYPE_DEX_OPT: + reporter.onReport(KEY_LOADED_MISSING_DEX_OPT); + break; + case ShareConstants.TYPE_DEX: + reporter.onReport(KEY_LOADED_MISSING_DEX); + break; + case ShareConstants.TYPE_LIBRARY: + reporter.onReport(KEY_LOADED_MISSING_LIB); + break; + case ShareConstants.TYPE_PATCH_FILE: + reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE); + break; + case ShareConstants.TYPE_PATCH_INFO: + reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO); + break; + case ShareConstants.TYPE_RESOURCE: + reporter.onReport(KEY_LOADED_MISSING_RES); + break; + } + } + + public static void onLoadFileMisMatch(int fileType) { + if (reporter == null) { + return; + } + switch (fileType) { + case ShareConstants.TYPE_DEX: + reporter.onReport(KEY_LOADED_MISMATCH_DEX); + break; + case ShareConstants.TYPE_LIBRARY: + reporter.onReport(KEY_LOADED_MISMATCH_LIB); + break; + case ShareConstants.TYPE_RESOURCE: + reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE); + break; + } + } + + public static void onLoadException(Throwable throwable, int errorCode) { + if (reporter == null) { + return; + } + boolean isDexCheckFail = false; + switch (errorCode) { + case ShareConstants.ERROR_LOAD_EXCEPTION_DEX: + if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) { + reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK); + isDexCheckFail = true; + TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage()); + } else { + reporter.onReport(KEY_LOADED_EXCEPTION_DEX); + TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage()); + } + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE: + reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE); + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT: + reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION); + break; + case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN: + reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION); + break; + } + //reporter exception, for dex check fail, we don't need to report stacktrace + if (!isDexCheckFail) { + reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable)); + } + } + + public static void onApplyPatchServiceStart() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLIED_START); + } + + public static void onApplyDexOptFail(Throwable throwable) { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLIED_DEXOPT); + reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable)); + } + + public static void onApplyInfoCorrupted() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLIED_INFO_CORRUPTED); + } + + public static void onApplyVersionCheckFail() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLIED_VERSION_CHECK); + } + + public static void onApplyExtractFail(int fileType) { + if (reporter == null) { + return; + } + switch (fileType) { + case ShareConstants.TYPE_DEX: + reporter.onReport(KEY_APPLIED_DEX_EXTRACT); + break; + case ShareConstants.TYPE_DEX_FOR_ART: + reporter.onReport(KEY_APPLIED_DEX_ART_EXTRACT); + break; + case ShareConstants.TYPE_LIBRARY: + reporter.onReport(KEY_APPLIED_LIB_EXTRACT); + break; + case ShareConstants.TYPE_PATCH_FILE: + reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT); + break; + case ShareConstants.TYPE_RESOURCE: + reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT); + break; + } + } + + public static void onApplied(boolean isUpgrade, long cost, boolean success) { + if (reporter == null) { + return; + } + if (success) { + reporter.onReport(KEY_APPLIED); + } + + if (isUpgrade) { + if (success) { + reporter.onReport(KEY_APPLIED_UPGRADE); + } else { + reporter.onReport(KEY_APPLIED_UPGRADE_FAIL); + } + + } else { + if (success) { + reporter.onReport(KEY_APPLIED_REPAIR); + } else { + reporter.onReport(KEY_APPLIED_REPAIR_FAIL); + } + } + + TinkerLog.i(TAG, "hp_report report apply cost = %d", cost); + + if (cost < 0L) { + TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost"); + return; + } + + if (cost <= 5000) { + if (success) { + reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS); + } else { + reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS); + } + } else if (cost <= 10 * 1000) { + if (success) { + reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS); + } else { + reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS); + } + } else if (cost <= 30 * 1000) { + if (success) { + reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS); + } else { + reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS); + } + } else if (cost <= 60 * 1000) { + if (success) { + reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS); + } else { + reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS); + } + } else { + if (success) { + reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER); + } else { + reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER); + } + } + } + + public static void onApplyPackageCheckFail(int errorCode) { + if (reporter == null) { + return; + } + TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode); + + switch (errorCode) { + case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND); + break; + case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED: + reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META); + break; + } + } + + public static void onApplyCrash(Throwable throwable) { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLIED_EXCEPTION); + reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable)); + } + + public static void onFastCrashProtect() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_CRASH_FAST_PROTECT); + } + + public static void onXposedCrash() { + if (reporter == null) { + return; + } + if (ShareTinkerInternals.isVmArt()) { + reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART); + } else { + reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK); + } + } + + public static void onReportRetryPatch() { + if (reporter == null) { + return; + } + reporter.onReport(KEY_APPLY_WITH_RETRY); + } + +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/service/SampleResultService.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/service/SampleResultService.java new file mode 100644 index 00000000..d98c3a50 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/service/SampleResultService.java @@ -0,0 +1,143 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; + +import com.tencent.tinker.lib.service.DefaultTinkerResultService; +import com.tencent.tinker.lib.service.PatchResult; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.lib.util.TinkerServiceInternals; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; + +import java.io.File; +import java.util.zip.ZipFile; + +import tinker.sample.android.util.Utils; + +/** + * optional, you can just use DefaultTinkerResultService + * we can restart process when we are at background or screen off + * Created by zhangshaowen on 16/4/13. + */ +public class SampleResultService extends DefaultTinkerResultService { + private static final String TAG = "Tinker.SampleResultService"; + + + @Override + public void onPatchResult(final PatchResult result) { + if (result == null) { + TinkerLog.e(TAG, "SampleResultService received null result!!!!"); + return; + } + TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString()); + + //first, we want to kill the recover process + TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + if (result.isSuccess) { + Toast.makeText(getApplicationContext(), "patch success, please restart process", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(getApplicationContext(), "patch fail, please check reason", Toast.LENGTH_LONG).show(); + } + } + }); + // is success and newPatch, it is nice to delete the raw file, and restart at once + // for old patch, you can't delete the patch file + if (result.isSuccess && result.isUpgradePatch) { + File rawFile = new File(result.rawPatchFilePath); + if (rawFile.exists()) { + TinkerLog.i(TAG, "save delete raw patch file"); + SharePatchFileUtil.safeDeleteFile(rawFile); + } + //not like TinkerResultService, I want to restart just when I am at background! + //if you have not install tinker this moment, you can use TinkerApplicationHelper api + if (checkIfNeedKill(result)) { + if (Utils.isBackground()) { + TinkerLog.i(TAG, "it is in background, just restart process"); + restartProcess(); + } else { + //we can wait process at background, such as onAppBackground + //or we can restart when the screen off + TinkerLog.i(TAG, "tinker wait screen to restart process"); + new ScreenState(getApplicationContext(), new ScreenState.IOnScreenOff() { + @Override + public void onScreenOff() { + restartProcess(); + } + }); + } + } else { + TinkerLog.i(TAG, "I have already install the newly patch version!"); + } + } + + //repair current patch fail, just clean! + if (!result.isSuccess && !result.isUpgradePatch) { + //if you have not install tinker this moment, you can use TinkerApplicationHelper api + Tinker.with(getApplicationContext()).cleanPatch(); + } + } + + /** + * you can restart your process through service or broadcast + */ + private void restartProcess() { + TinkerLog.i(TAG, "app is background now, i can kill quietly"); + //you can send service or broadcast intent to restart your process + android.os.Process.killProcess(android.os.Process.myPid()); + } + + static class ScreenState { + interface IOnScreenOff { + void onScreenOff(); + } + + ScreenState(Context context, final IOnScreenOff onScreenOffInterface) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + context.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent in) { + String action = in == null ? "" : in.getAction(); + TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action); + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + + context.unregisterReceiver(this); + + if (onScreenOffInterface != null) { + onScreenOffInterface.onScreenOff(); + } + } + } + }, filter); + } + } + +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/util/SampleApplicationContext.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/SampleApplicationContext.java new file mode 100644 index 00000000..509924de --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/SampleApplicationContext.java @@ -0,0 +1,28 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.util; + +import android.app.Application; +import android.content.Context; + +/** + * Created by zhangshaowen on 16/8/9. + */ +public class SampleApplicationContext { + public static Application application = null; + public static Context context = null; +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/util/TinkerManager.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/TinkerManager.java new file mode 100644 index 00000000..4106eceb --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/TinkerManager.java @@ -0,0 +1,106 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.util; + +import com.tencent.tinker.lib.listener.PatchListener; +import com.tencent.tinker.lib.patch.AbstractPatch; +import com.tencent.tinker.lib.patch.RepairPatch; +import com.tencent.tinker.lib.patch.UpgradePatch; +import com.tencent.tinker.lib.reporter.LoadReporter; +import com.tencent.tinker.lib.reporter.PatchReporter; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.loader.app.ApplicationLike; + +import tinker.sample.android.crash.SampleUncaughtExceptionHandler; +import tinker.sample.android.reporter.SampleLoadReporter; +import tinker.sample.android.reporter.SamplePatchListener; +import tinker.sample.android.reporter.SamplePatchReporter; +import tinker.sample.android.service.SampleResultService; + +/** + * Created by zhangshaowen on 16/7/3. + */ +public class TinkerManager { + private static final String TAG = "Tinker.TinkerManager"; + + private static ApplicationLike applicationLike; + private static SampleUncaughtExceptionHandler uncaughtExceptionHandler; + private static boolean isInstalled = false; + + public static void setTinkerApplicationLike(ApplicationLike appLike) { + applicationLike = appLike; + } + + public static ApplicationLike getTinkerApplicationLike() { + return applicationLike; + } + + public static void initFastCrashProtect() { + if (uncaughtExceptionHandler == null) { + uncaughtExceptionHandler = new SampleUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); + } + } + + public static void setUpgradeRetryEnable(boolean enable) { + UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable); + } + + + /** + * all use default class, simply Tinker install method + */ + public static void sampleInstallTinker(ApplicationLike appLike) { + if (isInstalled) { + TinkerLog.w(TAG, "install tinker, but has installed, ignore"); + return; + } + TinkerInstaller.install(appLike); + isInstalled = true; + + } + + /** + * you can specify all class you want. + * sometimes, you can only install tinker in some process you want! + * + * @param appLike + */ + public static void installTinker(ApplicationLike appLike) { + if (isInstalled) { + TinkerLog.w(TAG, "install tinker, but has installed, ignore"); + return; + } + //or you can just use DefaultLoadReporter + LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication()); + //or you can just use DefaultPatchReporter + PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication()); + //or you can just use DefaultPatchListener + PatchListener patchListener = new SamplePatchListener(appLike.getApplication()); + //you can set your own upgrade patch if you need + AbstractPatch upgradePatchProcessor = new UpgradePatch(); + //you can set your own repair patch if you need + AbstractPatch repairPatchProcessor = new RepairPatch(); + + TinkerInstaller.install(appLike, + loadReporter, patchReporter, patchListener, + SampleResultService.class, upgradePatchProcessor, repairPatchProcessor); + + isInstalled = true; + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/util/UpgradePatchRetry.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/UpgradePatchRetry.java new file mode 100644 index 00000000..e3f8eefa --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/UpgradePatchRetry.java @@ -0,0 +1,265 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.util; + +import android.content.Context; +import android.content.Intent; + +import com.tencent.tinker.lib.service.TinkerPatchService; +import com.tencent.tinker.lib.tinker.Tinker; +import com.tencent.tinker.lib.tinker.TinkerInstaller; +import com.tencent.tinker.lib.util.TinkerLog; +import com.tencent.tinker.lib.util.TinkerServiceInternals; +import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +import tinker.sample.android.reporter.SampleTinkerReport; + +/** + * optional + * tinker :patch process may killed by some reason, we can retry it to increase upgrade success rate + * if patch file is at sdcard, copy it to dataDir first. because some software may delete it. + * + * Created by zhangshaowen on 16/7/3. + */ +public class UpgradePatchRetry { + private static final String TAG = "Tinker.UpgradePatchRetry"; + + private static final String RETRY_INFO_NAME = "patch.retry"; + private static final String TEMP_PATCH_NAME = "temp.apk"; + + private static final String RETRY_FILE_MD5_PROPERTY = "md5"; + private static final String RETRY_COUNT_PROPERTY = "times"; + private static final int RETRY_MAX_COUNT = 2; + + private boolean isRetryEnable = false; + private File retryInfoFile = null; + private File tempPatchFile = null; + + private Context context = null; + private static UpgradePatchRetry sInstance; + + /** + * you must set after tinker has installed + * + * @param context + */ + public UpgradePatchRetry(Context context) { + this.context = context; + retryInfoFile = new File(SharePatchFileUtil.getPatchDirectory(context), RETRY_INFO_NAME); + tempPatchFile = new File(SharePatchFileUtil.getPatchDirectory(context), TEMP_PATCH_NAME); + } + + public static UpgradePatchRetry getInstance(Context context) { + if (sInstance == null) { + sInstance = new UpgradePatchRetry(context); + } + return sInstance; + } + + public void onPatchRetryLoad() { + if (!isRetryEnable) { + TinkerLog.w(TAG, "onPatchRetryLoad retry disabled, just return"); + return; + } + Tinker tinker = Tinker.with(context); + //only retry on main process + if (!tinker.isMainProcess()) { + TinkerLog.w(TAG, "onPatchRetryLoad retry is not main process, just return"); + return; + } + + if (!retryInfoFile.exists()) { + TinkerLog.w(TAG, "onPatchRetryLoad retry info not exist, just return"); + return; + } + + if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) { + TinkerLog.w(TAG, "onPatchRetryLoad tinker service is running, just return"); + return; + } + //must use temp file + String path = tempPatchFile.getAbsolutePath(); + if (path == null || !new File(path).exists()) { + TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is not exist, just return", path); + return; + } + TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is exist, retry to patch", path); + TinkerInstaller.onReceiveUpgradePatch(context, path); + SampleTinkerReport.onReportRetryPatch(); + } + + private void copyToTempFile(File patchFile) { + if (patchFile.getAbsolutePath().equals(tempPatchFile.getAbsolutePath())) { + return; + } + TinkerLog.w(TAG, "try copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath()); + + try { + SharePatchFileUtil.copyFileUsingStream(patchFile, tempPatchFile); + } catch (IOException e) { + } + } + + public void onPatchServiceStart(Intent intent) { + if (!isRetryEnable) { + TinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return"); + return; + } + + if (intent == null) { + TinkerLog.e(TAG, "onPatchServiceStart intent is null, just return"); + return; + } + + boolean isUpgrade = TinkerPatchService.getPatchUpgradeExtra(intent); + + if (!isUpgrade) { + TinkerLog.w(TAG, "onPatchServiceStart is not upgrade patch, just return"); + return; + } + + String path = TinkerPatchService.getPatchPathExtra(intent); + + if (path == null) { + TinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return"); + return; + } + + RetryInfo retryInfo; + File patchFile = new File(path); + + String patchMd5 = SharePatchFileUtil.getMD5(patchFile); + + if (retryInfoFile.exists()) { + retryInfo = RetryInfo.readRetryProperty(retryInfoFile); + if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) { + copyToTempFile(patchFile); + retryInfo.md5 = patchMd5; + retryInfo.times = "1"; + } else { + int nowTimes = Integer.parseInt(retryInfo.times); + if (nowTimes >= RETRY_MAX_COUNT) { + SharePatchFileUtil.safeDeleteFile(retryInfoFile); + SharePatchFileUtil.safeDeleteFile(tempPatchFile); + TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!"); + return; + } else { + retryInfo.times = String.valueOf(nowTimes + 1); + } + } + + } else { + copyToTempFile(patchFile); + retryInfo = new RetryInfo(patchMd5, "1"); + } + + RetryInfo.writeRetryProperty(retryInfoFile, retryInfo); + + } + + /** + * if we receive any result, we can delete the temp retry info file + * + * @param isUpgradePatch + */ + public void onPatchServiceResult(boolean isUpgradePatch) { + if (!isRetryEnable) { + TinkerLog.w(TAG, "onPatchServiceResult retry disabled, just return"); + return; + } + + if (!isUpgradePatch) { + TinkerLog.w(TAG, "onPatchServiceResult is not upgrade patch, just return"); + return; + } + + //delete info file + if (retryInfoFile.exists()) { + SharePatchFileUtil.safeDeleteFile(retryInfoFile); + } + //delete temp patch file + if (tempPatchFile.exists()) { + SharePatchFileUtil.safeDeleteFile(tempPatchFile); + } + } + + public void setRetryEnable(boolean enable) { + isRetryEnable = enable; + } + + static class RetryInfo { + String md5; + String times; + + RetryInfo(String md5, String times) { + this.md5 = md5; + this.times = times; + } + + static RetryInfo readRetryProperty(File infoFile) { + String md5 = null; + String times = null; + + Properties properties = new Properties(); + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(infoFile); + properties.load(inputStream); + md5 = properties.getProperty(RETRY_FILE_MD5_PROPERTY); + times = properties.getProperty(RETRY_COUNT_PROPERTY); + } catch (IOException e) { + e.printStackTrace(); + } finally { + SharePatchFileUtil.closeQuietly(inputStream); + } + + return new RetryInfo(md5, times); + } + + static void writeRetryProperty(File infoFile, RetryInfo info) { + if (info == null) { + return; + } + + File parentFile = infoFile.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + + Properties newProperties = new Properties(); + newProperties.put(RETRY_FILE_MD5_PROPERTY, info.md5); + newProperties.put(RETRY_COUNT_PROPERTY, info.times); + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(infoFile, false); + newProperties.store(outputStream, null); + } catch (Exception e) { +// e.printStackTrace(); + TinkerLog.printErrStackTrace(TAG, e, "retry write property fail"); + } finally { + SharePatchFileUtil.closeQuietly(outputStream); + } + + } + } +} diff --git a/tinker-sample-android/app/src/main/java/tinker/sample/android/util/Utils.java b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/Utils.java new file mode 100644 index 00000000..d6f2b993 --- /dev/null +++ b/tinker-sample-android/app/src/main/java/tinker/sample/android/util/Utils.java @@ -0,0 +1,156 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tinker.sample.android.util; + +import android.os.Environment; +import android.os.StatFs; + +import com.tencent.tinker.loader.shareutil.ShareConstants; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +/** + * Created by zhangshaowen on 16/4/7. + */ +public class Utils { + + /** + * the error code define by myself + * should after {@code ShareConstants.ERROR_PATCH_INSERVICE + */ + public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL = -5; + public static final int ERROR_PATCH_ROM_SPACE = -6; + public static final int ERROR_PATCH_MEMORY_LIMIT = -7; + public static final int ERROR_PATCH_ALREADY_APPLY = -8; + public static final int ERROR_PATCH_CRASH_LIMIT = -9; + public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -10; + + public static final String PLATFORM = "platform"; + + public static final int MIN_MEMORY_HEAP_SIZE = 45; + + private static boolean background = false; + + public static boolean isGooglePlay() { + return false; + } + + public static boolean isBackground() { + return background; + } + + public static void setBackground(boolean back) { + background = back; + } + + public static int checkForPatchRecover(long roomSize, int maxMemory) { + if (Utils.isGooglePlay()) { + return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL; + } + if (maxMemory < MIN_MEMORY_HEAP_SIZE) { + return Utils.ERROR_PATCH_MEMORY_LIMIT; + } + //or you can mention user to clean their rom space! + if (!checkRomSpaceEnough(roomSize)) { + return Utils.ERROR_PATCH_ROM_SPACE; + } + + return ShareConstants.ERROR_PATCH_OK; + } + + public static boolean isXposedExists(Throwable thr) { + StackTraceElement[] stackTraces = thr.getStackTrace(); + for (StackTraceElement stackTrace : stackTraces) { + final String clazzName = stackTrace.getClassName(); + if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) { + return true; + } + } + return false; + } + + @Deprecated + public static boolean checkRomSpaceEnough(long limitSize) { + long allSize; + long availableSize = 0; + try { + File data = Environment.getDataDirectory(); + StatFs sf = new StatFs(data.getPath()); + availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize(); + allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize(); + } catch (Exception e) { + allSize = 0; + } + + if (allSize != 0 && availableSize > limitSize) { + return true; + } + return false; + } + + public static String getExceptionCauseString(final Throwable ex) { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final PrintStream ps = new PrintStream(bos); + + try { + // print directly + Throwable t = ex; + while (t.getCause() != null) { + t = t.getCause(); + } + t.printStackTrace(ps); + return toVisualString(bos.toString()); + } finally { + try { + bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static String toVisualString(String src) { + boolean cutFlg = false; + + if (null == src) { + return null; + } + + char[] chr = src.toCharArray(); + if (null == chr) { + return null; + } + + int i = 0; + for (; i < chr.length; i++) { + if (chr[i] > 127) { + chr[i] = 0; + cutFlg = true; + break; + } + } + + if (cutFlg) { + return new String(chr, 0, i); + } else { + return src; + } + } +} diff --git a/tinker-sample-android/app/src/main/res/layout/activity_main.xml b/tinker-sample-android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..e7d4180a --- /dev/null +++ b/tinker-sample-android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,60 @@ + + + + + +