From a18867cbaa9bf1526b3a91f96d569c44f37e9d2b Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 11 Feb 2024 21:20:00 -0500 Subject: [PATCH 001/472] feat: Add Forgejo Actions workflow * Build debug artifacts for both EasyEUICC and OpenEUICC at every commit on master. * Release EasyEUICC apk when a tag is created. No OpenEUICC release will be uploaded. --- .forgejo/workflows/build-debug.yml | 44 +++++++++++++++++++++++++++ .forgejo/workflows/release.yml | 49 ++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 .forgejo/workflows/build-debug.yml create mode 100644 .forgejo/workflows/release.yml diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml new file mode 100644 index 0000000..4a55880 --- /dev/null +++ b/.forgejo/workflows/build-debug.yml @@ -0,0 +1,44 @@ +on: + push: + branches: + - 'master' + +jobs: + build-debug: + runs-on: docker + container: + volumes: + - android-app-keystore:/keystore + steps: + - name: Repository Checkout + uses: https://gitea.angry.im/actions/checkout@v3 + with: + submodules: recursive + + - name: Decode Secret Signing Configuration + uses: https://gitea.angry.im/actions/base64-to-file@v1 + with: + fileName: keystore.properties + fileDir: ${{ env.GITHUB_WORKSPACE }} + encodedString: ${{ secrets.OPENEUICC_SIGNING_CONFIG }} + + - name: Set up JDK 17 + uses: https://gitea.angry.im/actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: https://gitea.angry.im/actions/setup-android@v3 + + - name: Build Debug APKs + run: ./gradlew --no-daemon assembleDebug + + - name: Upload Artifacts + uses: https://gitea.angry.im/actions/upload-artifact@v3 + with: + name: Debug APKs + compression-level: 0 + path: | + app-unpriv/build/outputs/apk/debug/app-unpriv-debug.apk + app/build/outputs/apk/debug/app-debug.apk diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..d16f9e5 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,49 @@ +on: + push: + tags: '*' + +env: + # Enable reproducibility-related build system workarounds + REPRODUCIBLE_BUILD: true + +jobs: + release: + runs-on: docker + container: + volumes: + - android-app-keystore:/keystore + steps: + - name: Repository Checkout + uses: https://gitea.angry.im/actions/checkout@v3 + with: + submodules: recursive + + - name: Decode Secret Signing Configuration + uses: https://gitea.angry.im/actions/base64-to-file@v1 + with: + fileName: keystore.properties + fileDir: ${{ env.GITHUB_WORKSPACE }} + encodedString: ${{ secrets.OPENEUICC_SIGNING_CONFIG }} + + - name: Set up JDK 17 + uses: https://gitea.angry.im/actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: https://gitea.angry.im/actions/setup-android@v3 + + - name: Build Release APK (Unprivileged / EasyEUICC only) + run: ./gradlew --no-daemon :app-unpriv:assembleRelease + + - name: Create Release + uses: https://gitea.angry.im/actions/forgejo-release@v1 + with: + direction: upload + release-dir: app-unpriv/build/outputs/apk/release + url: https://gitea.angry.im + token: ${{ secrets.FORGEJO_TOKEN }} + # Release details are expected to be edited manually + release-notes: TBD + prerelease: 'true' From e587af971496aa7d29d3d73ba697dae597c32048 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Thu, 15 Feb 2024 19:07:30 -0500 Subject: [PATCH 002/472] workflows: Run only on runners with android app keystore --- .forgejo/workflows/build-debug.yml | 2 +- .forgejo/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml index 4a55880..80b0d8e 100644 --- a/.forgejo/workflows/build-debug.yml +++ b/.forgejo/workflows/build-debug.yml @@ -5,7 +5,7 @@ on: jobs: build-debug: - runs-on: docker + runs-on: [docker, android-app-certs] container: volumes: - android-app-keystore:/keystore diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index d16f9e5..529a6d8 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -8,7 +8,7 @@ env: jobs: release: - runs-on: docker + runs-on: [docker, android-app-certs] container: volumes: - android-app-keystore:/keystore From f90f44ee530c03d520eca22b347b7eecee48e79b Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 18 Feb 2024 13:52:48 -0500 Subject: [PATCH 003/472] Relicense lpac-jni to LGPLv2 to match lpac --- README.md | 34 ++- libs/lpac-jni/LICENSE | 502 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 533 insertions(+), 3 deletions(-) create mode 100644 libs/lpac-jni/LICENSE diff --git a/README.md b/README.md index fab6db6..3248a62 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,40 @@ FAQs Copyright === +Everything except `libs/lpac-jni`: + ``` Copyright 2022-2024 OpenEUICC contributors -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, version 2. -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -You should have received a copy of the GNU General Public License along with this program. If not, see . +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +``` + +`libs/lpac-jni`: + +``` +Copyright (C) 2022-2024 OpenEUICC contributiors + +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, version 2.1. + +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 ``` \ No newline at end of file diff --git a/libs/lpac-jni/LICENSE b/libs/lpac-jni/LICENSE new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/libs/lpac-jni/LICENSE @@ -0,0 +1,502 @@ + 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 informed 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 Section 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 library 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 of 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! From 77fcc14dcab5c08a2c8cd4e66485d6c2deb7ef07 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 18 Feb 2024 14:08:21 -0500 Subject: [PATCH 004/472] Rewrite parts of README --- README.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3248a62..250028e 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,15 @@ A fully free and open-source Local Profile Assistant implementation for Android There are two variants of this project: -- OpenEUICC: The full-fledged privileged variant. Intended to be run as a privileged system app (inside `/system/priv-app`) and serve as the system LPA. This can be used to manage all kinds of eSIM chips, embedded or removable. - - The privileged variant can be imported to build along with AOSP by simply placing this repository and its [dependencies](https://gitea.angry.im/PeterCxy/android_prebuilts_openeuicc-deps) inside the AOSP tree. - - Notes: - - This repository contains submodules. If inclusion in `manifest.xml` is required, remember to set the `sync-s` option. - - **Only the latest AOSP release** is supported for building. Older versions of AOSP are still compatible with the app itself, but it may not compile within the old AOSP trees. For older versions, consider building the app with `gradle` or a newer AOSP source tree and simply import as a prebuilt apk. -- EasyEUICC: Unprivileged version that can run as a user app. An eSIM chip must include the certificate of EasyEUICC in its ARA-M field in order to grant access without system privileges. This is intended for removable eSIM chips such as those provided by eSTK. - - Prebuilt EasyEUICC apks can be downloaded [here](https://gitea.angry.im/PeterCxy/OpenEUICC/releases) +- OpenEUICC: The full-fledged privileged variant. + - Due to its privilege requirement, OpenEUICC must be placed inside `/system/priv-app` and be signed with the platform certificate. + - The preferred way to including OpenEUICC in a system image is to [build it along with AOSP](#building--aosp-). +- EasyEUICC: Unprivileged version that can run as a user app. + - Due to obvious security requirements, EasyEUICC is only able to access eSIM chips whose [ARF/ARA](https://source.android.com/docs/core/connect/uicc#arf) contains the hash of EasyEUICC's signing certificate. + - Prebuilt release-mode EasyEUICC apks can be downloaded [here](https://gitea.angry.im/PeterCxy/OpenEUICC/releases) - For removable eSIM chip vendors: to have your chip supported by official builds of EasyEUICC, include the ARA-M hash `2A2FA878BC7C3354C2CF82935A5945A3EDAE4AFA` -Building +Building (Gradle) === Make sure you have all submodules cloned and updated by running @@ -48,11 +47,24 @@ For EasyEUICC: ./gradlew :app-unpriv:assembleRelease ``` +Building (AOSP) +=== + +There are two ways to include OpenEUICC in your AOSP-based system image: + +1. Include this project and its [dependencies](https://gitea.angry.im/PeterCxy/android_prebuilts_openeuicc-deps) inside the AOSP tree. + - If inclusion in `manifest.xml` is required, remember to set the `sync-s` option to clone submodules. + - The module name is `OpenEUICC`. You can include it in `PRODUCT_PACKAGES`, or simply build it standalone using `mm`. + - Compilation of this project is **only** tested against the latest AOSP release version. The app itself should be compatible with older AOSP versions, but the source may not compile against an older AOSP source tree. +2. If compilation against AOSP source tree is not possible, consider [building with gradle](#building--gradle-) and import the apk as a prebuilt. + - No official `Android.bp` is provided for this case but it should be straightforward to write. + - You might want to include `privapp_whitelist_im.angry.openeuicc.xml` as well. + FAQs === - Q: Do you provide prebuilt binaries for OpenEUICC? -- A: No. If you are a custom ROM developer, either include the entire OpenEUICC repository in your AOSP source tree, or generate an APK using `gradle` and import that as a prebuilt system app. Note that you might want `privapp_whitelist_im.angry.openeuicc.xml` as well. +- A: Debug-mode APKs are available continuously as an artifact of the [Actions](https://gitea.angry.im/PeterCxy/OpenEUICC/actions) CI used by this project. However, these debug-mode APKs are **not** intended for inclusion inside system images, nor are they supported by the developer in any sense. If you are a custom ROM developer, either include the entire OpenEUICC repository in your AOSP source tree, or generate an APK using `gradle` and import that as a prebuilt system app. Note that you might want `privapp_whitelist_im.angry.openeuicc.xml` as well. - Q: AOSP's Settings app seems to be confused by OpenEUICC (for example, disabling / enabling profiles from the Networks page do not work properly) - A: When your device has internal eSIM chip(s) __and__ you have inserted a removable eSIM chip, the Settings app can misbehave since it was never designed for this scenario. __Please prefer using OpenEUICC's own management interface whenever possible.__ In the future, there might be an option to exclude removable SIMs from being reported to the Android system. From 9f3977dc5ebdc05b9933dea608e8b31cbae8e7fb Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 18 Feb 2024 14:09:06 -0500 Subject: [PATCH 005/472] README: Fix fragments --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 250028e..a90df38 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ There are two variants of this project: - OpenEUICC: The full-fledged privileged variant. - Due to its privilege requirement, OpenEUICC must be placed inside `/system/priv-app` and be signed with the platform certificate. - - The preferred way to including OpenEUICC in a system image is to [build it along with AOSP](#building--aosp-). + - The preferred way to including OpenEUICC in a system image is to [build it along with AOSP](#building-aosp). - EasyEUICC: Unprivileged version that can run as a user app. - Due to obvious security requirements, EasyEUICC is only able to access eSIM chips whose [ARF/ARA](https://source.android.com/docs/core/connect/uicc#arf) contains the hash of EasyEUICC's signing certificate. - Prebuilt release-mode EasyEUICC apks can be downloaded [here](https://gitea.angry.im/PeterCxy/OpenEUICC/releases) @@ -56,7 +56,7 @@ There are two ways to include OpenEUICC in your AOSP-based system image: - If inclusion in `manifest.xml` is required, remember to set the `sync-s` option to clone submodules. - The module name is `OpenEUICC`. You can include it in `PRODUCT_PACKAGES`, or simply build it standalone using `mm`. - Compilation of this project is **only** tested against the latest AOSP release version. The app itself should be compatible with older AOSP versions, but the source may not compile against an older AOSP source tree. -2. If compilation against AOSP source tree is not possible, consider [building with gradle](#building--gradle-) and import the apk as a prebuilt. +2. If compilation against AOSP source tree is not possible, consider [building with gradle](#building-gradle) and import the apk as a prebuilt. - No official `Android.bp` is provided for this case but it should be straightforward to write. - You might want to include `privapp_whitelist_im.angry.openeuicc.xml` as well. From 1c7dc67803e6e2508700964ec555a38f1dca9b9e Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 18 Feb 2024 20:56:20 -0500 Subject: [PATCH 006/472] chore: Synchronize with upstream lpac changes --- .../java/net/typeblog/lpac_jni/EuiccInfo2.kt | 2 - libs/lpac-jni/src/main/jni/lpac | 2 +- .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 50 +++++++++++-------- .../main/jni/lpac-jni/lpac-notifications.c | 39 +++++++++------ 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt index 9cc1ddd..b7769e7 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt @@ -3,9 +3,7 @@ package net.typeblog.lpac_jni /* Corresponds to EuiccInfo2 in SGP.22 */ data class EuiccInfo2( val profileVersion: String, - val sgp22Version: String, val euiccFirmwareVersion: String, - val uiccFirmwareVersion: String, val globalPlatformVersion: String, val sasAccreditationNumber: String, val ppVersion: String, diff --git a/libs/lpac-jni/src/main/jni/lpac b/libs/lpac-jni/src/main/jni/lpac index 76baec7..c918053 160000 --- a/libs/lpac-jni/src/main/jni/lpac +++ b/libs/lpac-jni/src/main/jni/lpac @@ -1 +1 @@ -Subproject commit 76baec728ada6e9a7705bffc2e6bd68482acb839 +Subproject commit c9180539164521d491e63395b25538b80ad8b883 diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index fae39ff..9403c7e 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -53,7 +53,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2"); euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class); - euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V"); + euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V"); const char _unused[1]; empty_string = (*env)->NewString(env, _unused, 0); @@ -179,24 +179,35 @@ JNIEXPORT jobjectArray JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cGetProfilesInfo(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; struct es10c_profile_info *info = NULL; + struct es10c_profile_info *curr = NULL; jobjectArray ret = NULL; jobject jinfo = NULL; - int count; + int count = 0; + int i = 0; - if (es10c_get_profiles_info(ctx, &info, &count) < 0) { + if (es10c_get_profiles_info(ctx, &info) < 0) { return NULL; } + curr = info; + while (curr != NULL) { + curr = curr->next; + count++; + } + ret = (*env)->NewObjectArray(env, count, local_profile_info_class, NULL); // Convert the native info array to Java - for (int i = 0; i < count; i++) { - jinfo = profile_info_native_to_java(env, &info[i]); + curr = info; + while (curr != NULL) { + jinfo = profile_info_native_to_java(env, curr); (*env)->SetObjectArrayElement(env, ret, i, jinfo); (*env)->DeleteLocalRef(env, jinfo); + curr = curr->next; + i++; } - es10c_profile_info_free_all(info, count); + es10c_profile_info_free_all(info); return ret; } @@ -258,40 +269,37 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDeleteProfile(JNIEnv *env, jobject thiz JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10cex_euiccinfo2 info; + struct es10cex_euiccinfo2 * info; jstring sas_accreditation_number = NULL; jstring global_platform_version = NULL; jstring euicc_firmware_version = NULL; - jstring uicc_firmware_version = NULL; jstring profile_version = NULL; - jstring sgp22_version = NULL; jstring pp_version = NULL; jobject ret = NULL; if (es10cex_get_euiccinfo2(ctx, &info) < 0) goto out; - profile_version = toJString(env, info.profile_version); - sgp22_version = toJString(env, info.sgp22_version); - euicc_firmware_version = toJString(env, info.euicc_firmware_version); - uicc_firmware_version = toJString(env, info.uicc_firmware_version); - global_platform_version = toJString(env, info.global_platform_version); - sas_accreditation_number = toJString(env, info.sas_accreditation_number); - pp_version = toJString(env, info.pp_version); + profile_version = toJString(env, info->profileVersion); + euicc_firmware_version = toJString(env, info->euiccFirmwareVer); + global_platform_version = toJString(env, info->globalplatformVersion); + sas_accreditation_number = toJString(env, info->sasAcreditationNumber); + pp_version = toJString(env, info->ppVersion); ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor, - profile_version, sgp22_version, euicc_firmware_version, - uicc_firmware_version, global_platform_version, + profile_version, euicc_firmware_version, + global_platform_version, sas_accreditation_number, pp_version, - info.free_nvram, info.free_ram); + info->extCardResource.freeNonVolatileMemory, + info->extCardResource.freeVolatileMemory); out: (*env)->DeleteLocalRef(env, profile_version); - (*env)->DeleteLocalRef(env, sgp22_version); (*env)->DeleteLocalRef(env, euicc_firmware_version); - (*env)->DeleteLocalRef(env, uicc_firmware_version); (*env)->DeleteLocalRef(env, global_platform_version); (*env)->DeleteLocalRef(env, sas_accreditation_number); (*env)->DeleteLocalRef(env, pp_version); + if (info != NULL) + es10cex_free_euiccinfo2(info); return ret; } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index 1647b98..622e3f7 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -33,36 +33,48 @@ void lpac_notifications_init() { JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10b_notification_metadata *info; + struct es10b_notification_metadata *info = NULL; + struct es10b_notification_metadata *curr = NULL; jobject notification = NULL; jobject operation = NULL; jobjectArray ret = NULL; - int count; + int count = 0; + int i = 0; - if (es10b_list_notification(ctx, &info, &count) < 0) + if (es10b_list_notification(ctx, &info) < 0) return NULL; + curr = info; + while (curr != NULL) { + curr = curr->next; + count++; + } + ret = (*env)->NewObjectArray(env, count, local_profile_notification_class, NULL); - for (int i = 0; i < count; i++) { + curr = info; + while (curr != NULL) { operation = (*env)->CallStaticObjectMethod(env, local_profile_notification_operation_class, local_profile_notification_operation_from_string, - toJString(env, info[i].profileManagementOperation)); + toJString(env, curr->profileManagementOperation)); notification = (*env)->NewObject(env, local_profile_notification_class, - local_profile_notification_constructor, info[i].seqNumber, operation, - toJString(env, info[i].notificationAddress), - toJString(env, info[i].iccid)); + local_profile_notification_constructor, curr->seqNumber, operation, + toJString(env, curr->notificationAddress), + toJString(env, curr->iccid)); (*env)->SetObjectArrayElement(env, ret, i, notification); (*env)->DeleteLocalRef(env, operation); (*env)->DeleteLocalRef(env, notification); + + curr = curr->next; + i++; } - es10b_notification_metadata_free_all(info, count); + es10b_notification_metadata_free_all(info); return ret; } @@ -70,23 +82,20 @@ JNIEXPORT jint JNICALL Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz, jlong handle, jlong seq_number) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - char *b64_payload = NULL; - char *receiver = NULL; + struct es10b_notification notification; int res; - res = es10b_retrieve_notification(ctx, &b64_payload, &receiver, (unsigned long) seq_number); + res = es10b_retrieve_notification(ctx, ¬ification, (unsigned long) seq_number); syslog(LOG_DEBUG, "es10b_retrieve_notification = %d", res); if (res < 0) goto out; - res = es9p_handle_notification(ctx, receiver, b64_payload); + res = es9p_handle_notification(ctx, notification.receiver, notification.b64_payload); syslog(LOG_DEBUG, "es9p_handle_notification = %d", res); if (res < 0) goto out; out: - free(b64_payload); - free(receiver); return res; } From 1c0ddefad96d603cb493e8d56feea2fb202cb156 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 18 Feb 2024 21:08:37 -0500 Subject: [PATCH 007/472] lpac-jni: Introduce convenience macros for linked lists --- libs/lpac-jni/src/main/jni/Application.mk | 1 + libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 14 +++----------- libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h | 15 +++++++++++++++ .../src/main/jni/lpac-jni/lpac-notifications.c | 15 +++------------ 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/Application.mk b/libs/lpac-jni/src/main/jni/Application.mk index d945630..173c76f 100644 --- a/libs/lpac-jni/src/main/jni/Application.mk +++ b/libs/lpac-jni/src/main/jni/Application.mk @@ -1,3 +1,4 @@ APP_ABI := all APP_SHORT_COMMANDS := true +APP_CFLAGS := -Wno-compound-token-split-by-macro APP_LDFLAGS := -Wl,--build-id=none \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index 9403c7e..44f5e93 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -183,29 +183,21 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cGetProfilesInfo(JNIEnv *env, jobject th jobjectArray ret = NULL; jobject jinfo = NULL; int count = 0; - int i = 0; if (es10c_get_profiles_info(ctx, &info) < 0) { return NULL; } - curr = info; - while (curr != NULL) { - curr = curr->next; - count++; - } + count = LPAC_JNI_LINKED_LIST_COUNT(info, curr); ret = (*env)->NewObjectArray(env, count, local_profile_info_class, NULL); // Convert the native info array to Java - curr = info; - while (curr != NULL) { + LPAC_JNI_LINKED_LIST_FOREACH(info, curr, { jinfo = profile_info_native_to_java(env, curr); (*env)->SetObjectArrayElement(env, ret, i, jinfo); (*env)->DeleteLocalRef(env, jinfo); - curr = curr->next; - i++; - } + }); es10c_profile_info_free_all(info); return ret; diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h index 497d72f..e0e2109 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h @@ -13,6 +13,21 @@ struct lpac_jni_ctx { JNIEnv *env; \ (*jvm)->AttachCurrentThread(jvm, &env, NULL) +#define __LPAC_JNI_LINKED_LIST_FOREACH(list, curr, body, after) { \ + int i = 0; \ + curr = list; \ + while (curr != NULL) { \ + body; \ + curr = curr->next; \ + i++; \ + }; \ + after; \ +} +#define LPAC_JNI_LINKED_LIST_FOREACH(list, curr, body) \ + __LPAC_JNI_LINKED_LIST_FOREACH(list, curr, body, {}) +#define LPAC_JNI_LINKED_LIST_COUNT(list, curr) \ + (__LPAC_JNI_LINKED_LIST_FOREACH(list, curr, {}, i)) + extern JavaVM *jvm; extern jclass string_class; diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index 622e3f7..86255de 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -39,21 +39,15 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject t jobject operation = NULL; jobjectArray ret = NULL; int count = 0; - int i = 0; if (es10b_list_notification(ctx, &info) < 0) return NULL; - curr = info; - while (curr != NULL) { - curr = curr->next; - count++; - } + count = LPAC_JNI_LINKED_LIST_COUNT(info, curr); ret = (*env)->NewObjectArray(env, count, local_profile_notification_class, NULL); - curr = info; - while (curr != NULL) { + LPAC_JNI_LINKED_LIST_FOREACH(info, curr, { operation = (*env)->CallStaticObjectMethod(env, local_profile_notification_operation_class, local_profile_notification_operation_from_string, @@ -69,10 +63,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject t (*env)->DeleteLocalRef(env, operation); (*env)->DeleteLocalRef(env, notification); - - curr = curr->next; - i++; - } + }); es10b_notification_metadata_free_all(info); return ret; From 048764d30524302b4622684b019863cfdeaaf1b6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 19 Feb 2024 16:42:39 -0500 Subject: [PATCH 008/472] refactor: Comaptibility checks should return the success / failure state directly --- .../openeuicc/util/CompatibilityCheck.kt | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index 8350704..ad4b423 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -53,17 +53,13 @@ abstract class CompatibilityCheck(context: Context) { else -> defaultDescription } - protected abstract suspend fun doCheck(): Boolean + protected abstract suspend fun doCheck(): State suspend fun run() { state = State.IN_PROGRESS delay(200) state = try { - if (doCheck()) { - State.SUCCESS - } else { - State.FAILURE - } + doCheck() } catch (_: Exception) { State.FAILURE } @@ -76,10 +72,10 @@ internal class HasSystemFeaturesCheck(private val context: Context): Compatibili override val defaultDescription: String get() = context.getString(R.string.compatibility_check_system_features_desc) - override suspend fun doCheck(): Boolean { + override suspend fun doCheck(): State { if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { failureDescription = context.getString(R.string.compatibility_check_system_features_no_telephony) - return false + return State.FAILURE } // We can check OMAPI UICC availability on R or later (if before R, we check OMAPI connectivity later) @@ -87,10 +83,10 @@ internal class HasSystemFeaturesCheck(private val context: Context): Compatibili PackageManager.FEATURE_SE_OMAPI_UICC )) { failureDescription = context.getString(R.string.compatibility_check_system_features_no_omapi) - return false + return State.FAILURE } - return true + return State.SUCCESS } } @@ -100,25 +96,25 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck( override val defaultDescription: String get() = context.getString(R.string.compatibility_check_omapi_connectivity_desc) - override suspend fun doCheck(): Boolean { + override suspend fun doCheck(): State { val seService = connectSEService(context) if (!seService.isConnected) { failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail) - return false + return State.FAILURE } val tm = context.getSystemService(TelephonyManager::class.java) val simReaders = seService.readers.filter { it.isSIM } if (simReaders.isEmpty()) { failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail) - return false + return State.FAILURE } else if (simReaders.size < tm.activeModemCountCompat) { failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail_sim_number, simReaders.map { it.slotIndex }.joinToString(", ")) - return false + return State.FAILURE } - return true + return State.SUCCESS } } @@ -132,30 +128,30 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili override val defaultDescription: String get() = context.getString(R.string.compatibility_check_isdr_channel_desc) - override suspend fun doCheck(): Boolean { + override suspend fun doCheck(): State { val seService = connectSEService(context) val (validSlotIds, result) = seService.readers.filter { it.isSIM }.map { try { it.openSession().openLogicalChannel(ISDR_AID)?.close() - Pair(it.slotIndex, true) + Pair(it.slotIndex, State.SUCCESS) } catch (_: SecurityException) { // Ignore; this is expected when everything works // ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305 // SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed - Pair(it.slotIndex, true) + Pair(it.slotIndex, State.SUCCESS) } catch (e: Exception) { e.printStackTrace() - Pair(it.slotIndex, false) + Pair(it.slotIndex, State.FAILURE) } - }.fold(Pair(mutableListOf(), true)) { (ids, result), (id, ok) -> - if (!ok) { - Pair(ids, false) + }.fold(Pair(mutableListOf(), State.SUCCESS)) { (ids, result), (id, ok) -> + if (ok != State.SUCCESS) { + Pair(ids, ok) } else { Pair(ids.apply { add(id) }, result) } } - if (!result && validSlotIds.size > 0) { + if (result != State.SUCCESS && validSlotIds.size > 0) { if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) { failureDescription = context.getString( R.string.compatibility_check_isdr_channel_desc_partial_fail, @@ -164,7 +160,7 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili } else { // If the device has embedded eSIMs, we can likely ignore the failure here; // the OMAPI failure likely resulted from trying to access internal eSIMs. - return true + return State.SUCCESS } } @@ -186,6 +182,10 @@ internal class KnownBrokenCheck(private val context: Context): CompatibilityChec failureDescription = context.getString(R.string.compatibility_check_known_broken_fail) } - override suspend fun doCheck(): Boolean = - Build.MANUFACTURER.lowercase() !in BROKEN_MANUFACTURERS + override suspend fun doCheck(): State = + if (Build.MANUFACTURER.lowercase() in BROKEN_MANUFACTURERS) { + State.FAILURE + } else { + State.SUCCESS + } } \ No newline at end of file From 252000660a6cd390ca887a3b3c9920bc25bde168 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 19 Feb 2024 16:58:33 -0500 Subject: [PATCH 009/472] CompatibilityCheck: show unknown status when "secure element is not present" Some devices "optimize" their OMAPI by reporting this status when both slots are empty. Even just inserting one SIM would fix this error for both slots. In this case, we should not imply that the device is incompatible. --- .../angry/openeuicc/ui/CompatibilityCheckActivity.kt | 3 +++ .../im/angry/openeuicc/util/CompatibilityCheck.kt | 12 +++++++++++- .../src/main/res/drawable/ic_question_outline.xml | 5 +++++ .../src/main/res/layout/compatibility_check_item.xml | 8 ++++++++ app-unpriv/src/main/res/values/strings.xml | 1 + 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 app-unpriv/src/main/res/drawable/ic_question_outline.xml diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt index 20e0c5d..0eb433a 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt @@ -64,6 +64,9 @@ class CompatibilityCheckActivity: AppCompatActivity() { CompatibilityCheck.State.FAILURE -> { root.findViewById(R.id.compatibility_check_error).visibility = View.VISIBLE } + CompatibilityCheck.State.FAILURE_UNKNOWN -> { + root.findViewById(R.id.compatibility_check_unknown).visibility = View.VISIBLE + } else -> { root.findViewById(R.id.compatibility_check_progress_bar).visibility = View.VISIBLE } diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index ad4b423..3d9c47b 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -9,6 +9,7 @@ import im.angry.easyeuicc.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import java.io.IOException fun getCompatibilityChecks(context: Context): List = listOf( @@ -38,6 +39,7 @@ abstract class CompatibilityCheck(context: Context) { NOT_STARTED, IN_PROGRESS, SUCCESS, + FAILURE_UNKNOWN, // The check technically failed, but no conclusion can be drawn FAILURE } @@ -49,7 +51,7 @@ abstract class CompatibilityCheck(context: Context) { val description: String get() = when { - state == State.FAILURE && this::failureDescription.isInitialized -> failureDescription + (state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription else -> defaultDescription } @@ -139,6 +141,14 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili // ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305 // SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed Pair(it.slotIndex, State.SUCCESS) + } catch (e: IOException) { + e.printStackTrace() + if (e.message?.contains("Secure Element is not present") == true) { + failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown) + Pair(it.slotIndex, State.FAILURE_UNKNOWN) + } else { + Pair(it.slotIndex, State.FAILURE) + } } catch (e: Exception) { e.printStackTrace() Pair(it.slotIndex, State.FAILURE) diff --git a/app-unpriv/src/main/res/drawable/ic_question_outline.xml b/app-unpriv/src/main/res/drawable/ic_question_outline.xml new file mode 100644 index 0000000..8c0d6eb --- /dev/null +++ b/app-unpriv/src/main/res/drawable/ic_question_outline.xml @@ -0,0 +1,5 @@ + + + diff --git a/app-unpriv/src/main/res/layout/compatibility_check_item.xml b/app-unpriv/src/main/res/layout/compatibility_check_item.xml index f75741a..0e46668 100644 --- a/app-unpriv/src/main/res/layout/compatibility_check_item.xml +++ b/app-unpriv/src/main/res/layout/compatibility_check_item.xml @@ -60,6 +60,14 @@ android:layout_width="32dp" android:layout_height="32dp" /> + + \ No newline at end of file diff --git a/app-unpriv/src/main/res/values/strings.xml b/app-unpriv/src/main/res/values/strings.xml index 5d4b9db..3a20af1 100644 --- a/app-unpriv/src/main/res/values/strings.xml +++ b/app-unpriv/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ Only the following SIM slots are accessible via OMAPI: %s. ISD-R Channel Access Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI? + Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already. OMAPI access to ISD-R is only possible on the following SIM slots: %s. Known Broken? Making sure your device is not known to have bugs associated with removable eSIMs. From c033ef5ba9ad451d8504a92d76b802f7d51017c0 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 21 Feb 2024 21:09:20 -0500 Subject: [PATCH 010/472] refactor: Trust SM-DP+ TLS certs based on euiccCiPKIdListForVerification Unfortunately, because there is no way to access the certificate itself from the eUICC, we have to hard-code known & supported certificates still. However, this approach makes sure that only those certificates listed by the eUICC are trusted during their SM-DP+ sessions. Were these added directly as part of the Android security config, then all certificates would be blindly trusted for all SM-DP+ sessions (and even normal TLS connections if the app were to make them). As a result we can now trust more known certificates, including GSMA Test CIs. These are hard-coded as a hash map. --- app-common/src/main/AndroidManifest.xml | 3 +- .../main/res/raw/symantec_gsma_rspv2_root_ci1 | 15 -- .../main/res/xml/network_security_config.xml | 9 -- .../java/net/typeblog/lpac_jni/EuiccInfo2.kt | 2 + .../net/typeblog/lpac_jni/HttpInterface.kt | 6 + .../lpac_jni/impl/HttpInterfaceImpl.kt | 26 ++- .../impl/LocalProfileAssistantImpl.kt | 3 + .../lpac_jni/impl/RootCertificates.kt | 148 ++++++++++++++++++ .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 22 ++- .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.h | 15 ++ 10 files changed, 219 insertions(+), 30 deletions(-) delete mode 100644 app-common/src/main/res/raw/symantec_gsma_rspv2_root_ci1 delete mode 100644 app-common/src/main/res/xml/network_security_config.xml create mode 100644 libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index a9c61d3..be37bf4 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -6,8 +6,7 @@ - + diff --git a/app-common/src/main/res/raw/symantec_gsma_rspv2_root_ci1 b/app-common/src/main/res/raw/symantec_gsma_rspv2_root_ci1 deleted file mode 100644 index d9b7113..0000000 --- a/app-common/src/main/res/raw/symantec_gsma_rspv2_root_ci1 +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw -FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv -biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5 -NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz -c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH -A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n -unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ -BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h -dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E -FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF -WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO -aFsiLzIEOaUuZwdNUw== ------END CERTIFICATE----- diff --git a/app-common/src/main/res/xml/network_security_config.xml b/app-common/src/main/res/xml/network_security_config.xml deleted file mode 100644 index 2d4e014..0000000 --- a/app-common/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt index b7769e7..e69c7ff 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt @@ -9,4 +9,6 @@ data class EuiccInfo2( val ppVersion: String, val freeNvram: Int, val freeRam: Int, + val euiccCiPKIdListForSigning: Array, + val euiccCiPKIdListForVerification: Array, ) \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt index f693374..49d6cc0 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt @@ -1,5 +1,7 @@ package net.typeblog.lpac_jni +import javax.net.ssl.TrustManager + /* * Should reflect euicc_http_interface in lpac/euicc/interface.h */ @@ -25,4 +27,8 @@ interface HttpInterface { } fun transmit(url: String, tx: ByteArray, headers: Array): HttpResponse + // The LPA is supposed to pass in a list of pkIds supported by the eUICC. + // HttpInterface is responsible for providing TrustManager implementations that + // validate based on certificates corresponding to these pkIds + fun usePublicKeyIds(pkids: Array) } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt index d84ae95..bfad50c 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/HttpInterfaceImpl.kt @@ -2,14 +2,20 @@ package net.typeblog.lpac_jni.impl import android.util.Log import net.typeblog.lpac_jni.HttpInterface -import java.net.HttpURLConnection import java.net.URL +import java.security.SecureRandom +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.TrustManagerFactory class HttpInterfaceImpl: HttpInterface { companion object { private const val TAG = "HttpInterfaceImpl" } + private lateinit var trustManagers: Array + override fun transmit( url: String, tx: ByteArray, @@ -17,8 +23,17 @@ class HttpInterfaceImpl: HttpInterface { ): HttpInterface.HttpResponse { Log.d(TAG, "transmit(url = $url)") + val parsedUrl = URL(url) + if (parsedUrl.protocol != "https") { + throw IllegalArgumentException("SM-DP+ servers must use the HTTPS protocol") + } + try { - val conn = URL(url).openConnection() as HttpURLConnection + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, trustManagers, SecureRandom()) + + val conn = parsedUrl.openConnection() as HttpsURLConnection + conn.sslSocketFactory = sslContext.socketFactory conn.requestMethod = "POST" conn.doInput = true conn.doOutput = true @@ -40,4 +55,11 @@ class HttpInterfaceImpl: HttpInterface { throw e } } + + override fun usePublicKeyIds(pkids: Array) { + val trustManagerFactory = TrustManagerFactory.getInstance("PKIX").apply { + init(keyIdToKeystore(pkids)) + } + trustManagers = trustManagerFactory.trustManagers + } } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 5dabf28..7412f00 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -26,6 +26,9 @@ class LocalProfileAssistantImpl( if (LpacJni.es10xInit(contextHandle) < 0) { throw IllegalArgumentException("Failed to initialize LPA") } + + val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1) + httpInterface.usePublicKeyIds(pkids) } private fun tryReconnect(timeoutMillis: Long) = runBlocking { diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt new file mode 100644 index 0000000..ec094aa --- /dev/null +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -0,0 +1,148 @@ +package net.typeblog.lpac_jni.impl + +import java.io.ByteArrayInputStream +import java.security.KeyStore +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory + +const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb" + +internal fun keyIdToKeystore(keyIds: Array): KeyStore { + val ret = KeyStore.getInstance(KeyStore.getDefaultType()) + ret.load(null, null) + keyIds.forEach { + if (it !in KNOWN_CI_CERTS) throw CertificateException("Unknown CI cert ID $it") + ByteArrayInputStream(KNOWN_CI_CERTS[it]!!.toByteArray()).use { stream -> + val cf = CertificateFactory.getInstance("X.509") + ret.setCertificateEntry(it, cf.generateCertificate(stream)) + } + } + return ret +} + +// ref: +internal val KNOWN_CI_CERTS = hashMapOf( + // GSM Association - RSP2 Root CI1 (CA: DigiCert) + // Specs: SGP.21 and SGP.22 version 2 and version 3 + "81370f5125d0b1d408d4c3b232e6d25e795bebfb" to """ + -----BEGIN CERTIFICATE----- + MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw + FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv + biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5 + NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz + c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH + A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n + unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud + EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ + BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h + dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E + FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF + WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO + aFsiLzIEOaUuZwdNUw== + -----END CERTIFICATE----- + """.trimIndent(), + // OISITE GSMA CI G1 (CA: WISeKey) + // Specs: SGP.21 and SGP.22 version 3 + "4c27967ad20c14b391e9601e41e604ad57c0222f" to """ + -----BEGIN CERTIFICATE----- + MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw + QzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xGTAXBgNV + BAMMEE9JU1RFIEdTTUEgQ0kgRzEwIBcNMjQwMTE2MjMxNzM5WhgPMjA1OTAxMDcy + MzE3MzhaMEMxCzAJBgNVBAYTAkNIMRkwFwYDVQQKDBBPSVNURSBGb3VuZGF0aW9u + MRkwFwYDVQQDDBBPSVNURSBHU01BIENJIEcxMFkwEwYHKoZIzj0CAQYIKoZIzj0D + AQcDQgAEvZ3s3PFC4NgrCcCMmHJ6DJ66uzAHuLcvjJnOn+TtBNThS7YHLDyHCa2v + 7D+zTP+XTtgqgcLoB56Gha9EQQQ4xKNtMGswDwYDVR0TAQH/BAUwAwEB/zAQBgNV + HREECTAHiAVghXQFDjAXBgNVHSABAf8EDTALMAkGB2eBEgECAQAwHQYDVR0OBBYE + FEwnlnrSDBSzkelgHkHmBK1XwCIvMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD + AgNIADBFAiBVcywTj017jKpAQ+gwy4MqK2hQvzve6lkvQkgSP6ykHwIhAI0KFwCD + jnPbmcJsG41hUrWNlf+IcrMvFuYii0DasBNi + -----END CERTIFICATE----- + """.trimIndent(), + // Symantec RSP Test Root CA (CA: DigiCert) + "665a1433d67c1a2c5db8b52c967f10a057ba5cb2" to """ + -----BEGIN CERTIFICATE----- + MIICkDCCAjagAwIBAgIQPfCO5OYL+cdbbx2ETDO7DDAKBggqhkjOPQQDAjBoMR0w + GwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjFHMEUGA1UEAxM+U3ltYW50ZWMg + Q29ycG9yYXRpb24gUlNQIFRlc3QgUm9vdCBDQSAtIEZvciBUZXN0IFB1cnBvc2Vz + IE9ubHkwHhcNMTcwNzExMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBoMR0wGwYDVQQK + ExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjFHMEUGA1UEAxM+U3ltYW50ZWMgQ29ycG9y + YXRpb24gUlNQIFRlc3QgUm9vdCBDQSAtIEZvciBUZXN0IFB1cnBvc2VzIE9ubHkw + WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQlbEYt9PTmdWcaX5WC68SYTFyZcbBN + vFpJW6bZQpERlMIAuzEpgscbTDccHtNpDqJwMqZXCO7ebCmRLyI6jqe3o4HBMIG+ + MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MBcGA1UdIAEB/wQNMAsw + CQYHZ4ESAQIBADBPBgNVHR8ESDBGMESgQqBAhj5odHRwOi8vcGtpLWNybC5zeW1h + dXRoLmNvbS9TeW1hbnRlY1JTUFRlc3RSb290Q0EvTGF0ZXN0Q1JMLmNybDASBgNV + HREECzAJiAcrBgEEAYMJMB0GA1UdDgQWBBRmWhQz1nwaLF24tSyWfxCgV7pcsjAK + BggqhkjOPQQDAgNIADBFAiAQ1quTqcexvDnKvmAkqoQP09QMXAXxlCyma82NtrYq + UQIhAP/W6pRamBGhSliV+EancgbZj+VoOkKdj0o7sP/cKdhZ + -----END CERTIFICATE----- + """.trimIndent(), + // GSMA Test CI + // Specs: SGP.26 v1 + "f54172bdf98a95d65cbeb88a38a1c11d800a85c3" to """ + -----BEGIN CERTIFICATE----- + MIICVjCCAfugAwIBAgIJALh086v6bETTMAoGCCqGSM49BAMCMEkxFTATBgNVBAMM + DEdTTUEgVGVzdCBDSTERMA8GA1UECwwIVEVTVENFUlQxEDAOBgNVBAoMB1JTUFRF + U1QxCzAJBgNVBAYTAklUMCAXDTE3MDIwMTE1Mzk0MloYDzIwNTIwMjAxMTUzOTQy + WjBJMRUwEwYDVQQDDAxHU01BIFRlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAw + DgYDVQQKDAdSU1BURVNUMQswCQYDVQQGEwJJVDBZMBMGByqGSM49AgEGCCqGSM49 + AwEHA0IABJQGV6Zz3CiPidUuqKR3BJknkfnDSwA25jPi0MupRU1l2zLrF5gXmdLy + Q4juK5XBCUVGyXkBzq66llCRmi4g0imjgckwgcYwHQYDVR0OBBYEFPVBcr35ipXW + XL64ijihwR2ACoXDMA8GA1UdEwEB/wQFMAMBAf8wFwYDVR0gAQH/BA0wCzAJBgdn + gRIBAgEAMA4GA1UdDwEB/wQEAwIBBjAOBgNVHREEBzAFiAOINwEwWwYDVR0fBFQw + UjAnoCWgI4YhaHR0cDovL2NpLnRlc3QuZ3NtYS5jb20vQ1JMLUEuY3JsMCegJaAj + hiFodHRwOi8vY2kudGVzdC5nc21hLmNvbS9DUkwtQi5jcmwwCgYIKoZIzj0EAwID + SQAwRgIhAJHyUclxU7nPhTeadItXKkloUkVWxH8z62l7VZEswPLSAiEA3OSec/NJ + 7NBZEO+d9raahnq/OJ3Ia4QRtN/hlpFQ9fk= + -----END CERTIFICATE----- + """.trimIndent(), + "c0bc70ba36929d43b467ff57570530e57ab8fcd8" to """ + -----BEGIN CERTIFICATE----- + MIICVTCCAfygAwIBAgIJALh086v6bETTMAoGCCqGSM49BAMCMEkxFTATBgNVBAMM + DEdTTUEgVGVzdCBDSTERMA8GA1UECwwIVEVTVENFUlQxEDAOBgNVBAoMB1JTUFRF + U1QxCzAJBgNVBAYTAklUMCAXDTE3MDQxOTEwMzQzOFoYDzIwNTIwNDE4MTAzNDM4 + WjBJMRUwEwYDVQQDDAxHU01BIFRlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAw + DgYDVQQKDAdSU1BURVNUMQswCQYDVQQGEwJJVDBaMBQGByqGSM49AgEGCSskAwMC + CAEBBwNCAAQnh7TVbtgkqea+BOGIf2uYuv64x2P1KDf3ARPpNk7dvhObwMfefhaP + HlzQwYU0/FBk5k9obcUzJ8p/Hc6oWimUo4HJMIHGMB0GA1UdDgQWBBTAvHC6NpKd + Q7Rn/1dXBTDlerj82DAPBgNVHRMBAf8EBTADAQH/MBcGA1UdIAEB/wQNMAswCQYH + Z4ESAQIBADAOBgNVHQ8BAf8EBAMCAQYwDgYDVR0RBAcwBYgDiDcBMFsGA1UdHwRU + MFIwJ6AloCOGIWh0dHA6Ly9jaS50ZXN0LmdzbWEuY29tL0NSTC1BLmNybDAnoCWg + I4YhaHR0cDovL2NpLnRlc3QuZ3NtYS5jb20vQ1JMLUIuY3JsMAoGCCqGSM49BAMC + A0cAMEQCIAbGw59auXFdcsVl3PX/O/7z+3DvCuM6BzZmkTWG0O+SAiAWskTrRp8q + L1hrwcMgtLZDG4902UH38YMrQyVSsWHfog== + -----END CERTIFICATE----- + """.trimIndent(), + // Test CI + // Specs: SGP.26 v3 + "34eecf13156518d48d30bdf06853404d115f955d" to """ + -----BEGIN CERTIFICATE----- + MIIB4zCCAYqgAwIBAgIBADAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ + MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC + SVQwIBcNMjMwNTMxMTI1MDI4WhgPMjA1ODA1MzAxMjUwMjhaMEQxEDAOBgNVBAMM + B1Rlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAwDgYDVQQKDAdSU1BURVNUMQsw + CQYDVQQGEwJJVDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLlQW4kHaMJSrAK4 + nVKjGIgKWYxick+Y1x0MKO/Bsb3+KxMdnAObkPZjLosKlKCnH2bHUHhqRyDDSc2Y + 9+wB6A6jazBpMB0GA1UdDgQWBBQ07s8TFWUY1I0wvfBoU0BNEV+VXTAOBgNVHQ8B + Af8EBAMCAQYwFwYDVR0gAQH/BA0wCzAJBgdngRIBAgEAMA8GA1UdEwEB/wQFMAMB + Af8wDgYDVR0RBAcwBYgDiDcBMAoGCCqGSM49BAMCA0cAMEQCIEuYVB+bwdn5Z6sL + eKFS07FnvHY03QqDm8XYxdjDAxZuAiBneNr+fBYeqDulQWfrGXFLDTbsFBENNdDj + jvcHgHpATQ== + -----END CERTIFICATE----- + """.trimIndent(), + "2209f61cd9ec5c9c854e787341ff83ecf9776a5b" to """ + -----BEGIN CERTIFICATE----- + MIIB5DCCAYugAwIBAgIBADAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ + MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC + SVQwIBcNMjMwNjAyMTMwNTQzWhgPMjA1ODA2MDExMzA1NDNaMEQxEDAOBgNVBAMM + B1Rlc3QgQ0kxETAPBgNVBAsMCFRFU1RDRVJUMRAwDgYDVQQKDAdSU1BURVNUMQsw + CQYDVQQGEwJJVDBaMBQGByqGSM49AgEGCSskAwMCCAEBBwNCAASF7cCXanl/xSJe + PwIeEUeZk4zPPM3iE16JbpOWPqPXaJwGmMKvHwQlRxiLtPWrRBalgkzrr4RgYIqD + aTcnvxoFo2swaTAdBgNVHQ4EFgQUIgn2HNnsXJyFTnhzQf+D7Pl3alswDgYDVR0P + AQH/BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAPBgNVHRMBAf8EBTAD + AQH/MA4GA1UdEQQHMAWIA4g3ATAKBggqhkjOPQQDAgNHADBEAiBLLHbhrIvy1Cue + 7lDUlQZY2EOK7/I/o2CQO0pj76OqzQIgTQ+kE02RPbMuflDbXKRuVDKFvfZ/vHEW + QKvBPWehIXI= + -----END CERTIFICATE----- + """.trimIndent() +) \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index 44f5e93..4c3393a 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -53,7 +53,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2"); euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class); - euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V"); + euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[Ljava/lang/String;[Ljava/lang/String;)V"); const char _unused[1]; empty_string = (*env)->NewString(env, _unused, 0); @@ -262,12 +262,16 @@ JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; struct es10cex_euiccinfo2 * info; + jobjectArray euiccCiPKIdListForVerification = NULL; + jobjectArray euiccCiPKIdListForSigning = NULL; jstring sas_accreditation_number = NULL; jstring global_platform_version = NULL; jstring euicc_firmware_version = NULL; jstring profile_version = NULL; jstring pp_version = NULL; jobject ret = NULL; + char **curr = NULL; + int count = 0; if (es10cex_get_euiccinfo2(ctx, &info) < 0) goto out; @@ -278,12 +282,26 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th sas_accreditation_number = toJString(env, info->sasAcreditationNumber); pp_version = toJString(env, info->ppVersion); + count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForSigning, curr); + euiccCiPKIdListForSigning = (*env)->NewObjectArray(env, count, string_class, NULL); + LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForSigning, curr, { + (*env)->SetObjectArrayElement(env, euiccCiPKIdListForSigning, i, toJString(env, *curr)); + }); + + count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForVerification, curr); + euiccCiPKIdListForVerification = (*env)->NewObjectArray(env, count, string_class, NULL); + LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForVerification, curr, { + (*env)->SetObjectArrayElement(env, euiccCiPKIdListForVerification, i, toJString(env, *curr)); + }); + ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor, profile_version, euicc_firmware_version, global_platform_version, sas_accreditation_number, pp_version, info->extCardResource.freeNonVolatileMemory, - info->extCardResource.freeVolatileMemory); + info->extCardResource.freeVolatileMemory, + euiccCiPKIdListForSigning, + euiccCiPKIdListForVerification); out: (*env)->DeleteLocalRef(env, profile_version); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h index e0e2109..e60c465 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h @@ -28,6 +28,21 @@ struct lpac_jni_ctx { #define LPAC_JNI_LINKED_LIST_COUNT(list, curr) \ (__LPAC_JNI_LINKED_LIST_FOREACH(list, curr, {}, i)) +#define __LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body, after) { \ + int i = 0; \ + curr = list; \ + while (*curr != NULL) { \ + body; \ + curr++; \ + i++; \ + }; \ + after; \ +} +#define LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body) \ + __LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, body, {}) +#define LPAC_JNI_NULL_TERM_LIST_COUNT(list, curr) \ + (__LPAC_JNI_NULL_TERM_LIST_FOREACH(list, curr, {}, i)) + extern JavaVM *jvm; extern jclass string_class; From ab76ae66e24f4d2a752843ae708ee20fc07c8c19 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 21 Feb 2024 21:26:45 -0500 Subject: [PATCH 011/472] lpac-jni: Do not crash on unknown CIs --- .../impl/LocalProfileAssistantImpl.kt | 2 +- .../lpac_jni/impl/RootCertificates.kt | 24 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 7412f00..f1367ce 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -27,7 +27,7 @@ class LocalProfileAssistantImpl( throw IllegalArgumentException("Failed to initialize LPA") } - val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1) + val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf() httpInterface.usePublicKeyIds(pkids) } diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt index ec094aa..80aa586 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -2,21 +2,35 @@ package net.typeblog.lpac_jni.impl import java.io.ByteArrayInputStream import java.security.KeyStore -import java.security.cert.CertificateException +import java.security.cert.Certificate import java.security.cert.CertificateFactory const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb" +private fun getCertificate(keyId: String): Certificate? = + KNOWN_CI_CERTS[keyId]?.toByteArray().let { cert -> + ByteArrayInputStream(cert).use { stream -> + val cf = CertificateFactory.getInstance("X.509") + cf.generateCertificate(stream) + } + } + internal fun keyIdToKeystore(keyIds: Array): KeyStore { val ret = KeyStore.getInstance(KeyStore.getDefaultType()) ret.load(null, null) keyIds.forEach { - if (it !in KNOWN_CI_CERTS) throw CertificateException("Unknown CI cert ID $it") - ByteArrayInputStream(KNOWN_CI_CERTS[it]!!.toByteArray()).use { stream -> - val cf = CertificateFactory.getInstance("X.509") - ret.setCertificateEntry(it, cf.generateCertificate(stream)) + getCertificate(it)?.let { cert -> + ret.setCertificateEntry(it, cert) } } + + // If no known certs have been added, add at least the default GSMA CI + if (ret.size() == 0) { + getCertificate(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1)?.let { cert -> + ret.setCertificateEntry(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1, cert) + } + } + return ret } From 2b972badaa92c05651906a3a73925e55b6fed570 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 21 Feb 2024 21:28:54 -0500 Subject: [PATCH 012/472] lpac-jni: Add Entrust CI to known list --- .../lpac_jni/impl/RootCertificates.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt index 80aa586..84fa227 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -158,5 +158,26 @@ internal val KNOWN_CI_CERTS = hashMapOf( 7lDUlQZY2EOK7/I/o2CQO0pj76OqzQIgTQ+kE02RPbMuflDbXKRuVDKFvfZ/vHEW QKvBPWehIXI= -----END CERTIFICATE----- + """.trimIndent(), + // Entrust eSIM Certification Authority (CA: Entrust) + "16704b7f351e3607f18c4b70005c3a003dfd414a" to """ + -----BEGIN CERTIFICATE----- + MIIC6DCCAo2gAwIBAgIRAIy4GT7M5nHsAAAAAFgsinowCgYIKoZIzj0EAwIwgbkx + CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9T + ZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAx + NiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNV + BAMTJEVudHJ1c3QgZVNJTSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAgFw0xNjEx + MTYxNjA0MDJaGA8yMDUxMTAxNjE2MzQwMlowgbkxCzAJBgNVBAYTAlVTMRYwFAYD + VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 + L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNiBFbnRydXN0LCBJbmMuIC0g + Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNVBAMTJEVudHJ1c3QgZVNJTSBD + ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA + BAdzwGHeQ1Wb2f4DmHTByR5/IWL3JugQ1U3908a++bHdlt+TTA7K4c5cYZ+51Yz/ + hg/bacxguPDh9uQUK6Wg3a6jcjBwMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ + BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAVBgNVHREEDjAMiApghkgB + hvpsFAoAMB0GA1UdDgQWBBQWcEt/NR42B/GMS3AAXDoAPf1BSjAKBggqhkjOPQQD + AgNJADBGAiEAspjXMvaBZyAg86Z0AAtT0yBRAi1EyaAfNz9kDJeAE04CIQC3efj8 + ATL7/tDBOhANy3cK8PS/1NIlu9vqMLCZsZvJ0Q== + -----END CERTIFICATE----- """.trimIndent() ) \ No newline at end of file From 5aed27513fa5ed7f13430efb1fd332a31e22bbde Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 24 Feb 2024 15:53:58 -0500 Subject: [PATCH 013/472] lpac-jni: Uprev lpac --- .../java/net/typeblog/lpac_jni/LpacJni.kt | 5 +- .../impl/LocalProfileAssistantImpl.kt | 10 +-- libs/lpac-jni/src/main/jni/lpac | 2 +- .../src/main/jni/lpac-jni/lpac-download.c | 30 +++---- .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 86 +++++++++++++------ .../main/jni/lpac-jni/lpac-notifications.c | 34 ++++++-- 6 files changed, 106 insertions(+), 61 deletions(-) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt index eb027bf..836332e 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt @@ -8,9 +8,8 @@ internal object LpacJni { external fun createContext(apduInterface: ApduInterface, httpInterface: HttpInterface): Long external fun destroyContext(handle: Long) - // es10x - external fun es10xInit(handle: Long): Int - external fun es10xFini(handle: Long) + external fun euiccInit(handle: Long): Int + external fun euiccFini(handle: Long) // es10c // null returns signify errors diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index f1367ce..072fcaa 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -23,7 +23,7 @@ class LocalProfileAssistantImpl( private var contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface) init { - if (LpacJni.es10xInit(contextHandle) < 0) { + if (LpacJni.euiccInit(contextHandle) < 0) { throw IllegalArgumentException("Failed to initialize LPA") } @@ -34,7 +34,7 @@ class LocalProfileAssistantImpl( private fun tryReconnect(timeoutMillis: Long) = runBlocking { withTimeout(timeoutMillis) { try { - LpacJni.es10xFini(contextHandle) + LpacJni.euiccFini(contextHandle) LpacJni.destroyContext(contextHandle) contextHandle = -1 } catch (e: Exception) { @@ -51,7 +51,7 @@ class LocalProfileAssistantImpl( try { apduInterface.connect() contextHandle = LpacJni.createContext(apduInterface, httpInterface) - check(LpacJni.es10xInit(contextHandle) >= 0) { "Reconnect attempt failed" } + check(LpacJni.euiccInit(contextHandle) >= 0) { "Reconnect attempt failed" } // Validate that we can actually use the APDU channel by trying to read eID and profiles check(valid) { "Reconnected channel is invalid" } break @@ -59,7 +59,7 @@ class LocalProfileAssistantImpl( e.printStackTrace() if (contextHandle != -1L) { try { - LpacJni.es10xFini(contextHandle) + LpacJni.euiccFini(contextHandle) LpacJni.destroyContext(contextHandle) contextHandle = -1 } catch (e: Exception) { @@ -133,7 +133,7 @@ class LocalProfileAssistantImpl( } override fun close() { - LpacJni.es10xFini(contextHandle) + LpacJni.euiccFini(contextHandle) LpacJni.destroyContext(contextHandle) } } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac b/libs/lpac-jni/src/main/jni/lpac index c918053..d06a2a0 160000 --- a/libs/lpac-jni/src/main/jni/lpac +++ b/libs/lpac-jni/src/main/jni/lpac @@ -1 +1 @@ -Subproject commit c9180539164521d491e63395b25538b80ad8b883 +Subproject commit d06a2a0e1f9bd29d86bc4f0881a84f67cd708aa3 diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index 2517d21..5674128 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -42,13 +42,13 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j jstring imei, jstring confirmation_code, jobject callback) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es9p_get_bound_profile_package_resp es9p_get_bound_profile_package_resp; - struct es9p_initiate_authentication_resp es9p_initiate_authentication_resp; + struct es9p_ctx es9p_ctx = { 0 }; + struct es10b_load_bound_profile_package_result es10b_load_bound_profile_package_result; struct es10b_authenticate_server_param es10b_authenticate_server_param; - struct es9p_authenticate_client_resp es9p_authenticate_client_resp; struct es10b_prepare_download_param es10b_prepare_download_param; char *b64_authenticate_server_response = NULL; char *b64_prepare_download_response = NULL; + char *b64_bound_profile_package = NULL; char *b64_euicc_challenge = NULL; char *b64_euicc_info_1 = NULL; char *transaction_id = NULL; @@ -66,6 +66,9 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j if (imei != NULL) _imei = (*env)->GetStringUTFChars(env, imei, NULL); + es9p_ctx.euicc_ctx = ctx; + es9p_ctx.address = _smdp; + (*env)->CallVoidMethod(env, callback, on_state_update, download_state_preparing); ret = es10b_get_euicc_challenge(ctx, &b64_euicc_challenge); if (ret < 0) @@ -76,45 +79,36 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_connecting); - ret = es9p_initiate_authentication(ctx, _smdp, b64_euicc_challenge, b64_euicc_info_1, &es9p_initiate_authentication_resp); + ret = es9p_initiate_authentication(&es9p_ctx, &es10b_authenticate_server_param, b64_euicc_challenge, b64_euicc_info_1); if (ret < 0) goto out; - transaction_id = strdup(es9p_initiate_authentication_resp.transaction_id); - es10b_authenticate_server_param.b64_server_signed_1 = es9p_initiate_authentication_resp.b64_server_signed_1; - es10b_authenticate_server_param.b64_server_signature_1 = es9p_initiate_authentication_resp.b64_server_signature_1; - es10b_authenticate_server_param.b64_euicc_ci_pkid_to_be_used = es9p_initiate_authentication_resp.b64_euicc_ci_pkid_to_be_used; - es10b_authenticate_server_param.b64_server_certificate = es9p_initiate_authentication_resp.b64_server_certificate; es10b_authenticate_server_param.matchingId = _matching_id; es10b_authenticate_server_param.imei = _imei; - es10b_authenticate_server_param.tac = NULL; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_authenticating); ret = es10b_authenticate_server(ctx, &b64_authenticate_server_response, &es10b_authenticate_server_param); if (ret < 0) goto out; - ret = es9p_authenticate_client(ctx, _smdp, transaction_id, b64_authenticate_server_response, &es9p_authenticate_client_resp); + ret = es9p_authenticate_client(&es9p_ctx, &es10b_prepare_download_param, b64_authenticate_server_response); if (ret < 0) goto out; - es10b_prepare_download_param.b64_smdp_signed_2 = es9p_authenticate_client_resp.b64_smdp_signed_2; - es10b_prepare_download_param.b64_smdp_signature_2 = es9p_authenticate_client_resp.b64_smdp_signature_2; - es10b_prepare_download_param.b64_smdp_certificate = es9p_authenticate_client_resp.b64_smdp_certificate; - es10b_prepare_download_param.hexstr_transcation_id = transaction_id; - es10b_prepare_download_param.str_checkcode = _confirmation_code; + es10b_prepare_download_param.confirmationCode = _confirmation_code; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading); ret = es10b_prepare_download(ctx, &b64_prepare_download_response, &es10b_prepare_download_param); if (ret < 0) goto out; - ret = es9p_get_bound_profile_package(ctx, _smdp, transaction_id, b64_prepare_download_response, &es9p_get_bound_profile_package_resp); + ret = es9p_get_bound_profile_package(&es9p_ctx, &b64_bound_profile_package, b64_prepare_download_response); if (ret < 0) goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_finalizing); - ret = es10b_load_bound_profile_package(ctx, es9p_get_bound_profile_package_resp.b64_bpp); + // TODO: Expose error code as Java-side exceptions? + ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result, b64_bound_profile_package); out: free(b64_authenticate_server_response); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index 4c3393a..dca47f4 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -1,4 +1,6 @@ -#include +#include +#include +#include #include #include #include @@ -93,15 +95,15 @@ Java_net_typeblog_lpac_1jni_LpacJni_destroyContext(JNIEnv *env, jobject thiz, jl } JNIEXPORT jint JNICALL -Java_net_typeblog_lpac_1jni_LpacJni_es10xInit(JNIEnv *env, jobject thiz, jlong handle) { +Java_net_typeblog_lpac_1jni_LpacJni_euiccInit(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - return es10x_init(ctx); + return euicc_init(ctx); } JNIEXPORT void JNICALL -Java_net_typeblog_lpac_1jni_LpacJni_es10xFini(JNIEnv *env, jobject thiz, jlong handle) { +Java_net_typeblog_lpac_1jni_LpacJni_euiccFini(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - es10x_fini(ctx); + euicc_fini(ctx); } jstring toJString(JNIEnv *env, const char *pat) { @@ -137,7 +139,9 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cGetEid(JNIEnv *env, jobject thiz, jlong return ret; } -jobject profile_info_native_to_java(JNIEnv *env, struct es10c_profile_info *info) { +jobject profile_info_native_to_java(JNIEnv *env, struct es10c_profile_info_list *info) { + const char *profileStateStr = NULL; + const char *profileClassStr = NULL; jstring serviceProvider = NULL; jstring nickName = NULL; jstring isdpAid = NULL; @@ -153,13 +157,40 @@ jobject profile_info_native_to_java(JNIEnv *env, struct es10c_profile_info *info nickName = toJString(env, info->profileNickname); serviceProvider = toJString(env, info->serviceProviderName); + // TODO: Maybe we should pass a Java object directly here? + switch (info->profileState) { + case ES10C_PROFILE_STATE_ENABLED: + profileStateStr = "enabled"; + break; + case ES10C_PROFILE_STATE_DISABLED: + profileStateStr = "disabled"; + break; + default: + profileStateStr = "unknown"; + } + state = (*env)->CallStaticObjectMethod(env, local_profile_state_class, local_profile_state_from_string, - toJString(env, info->profileState)); + toJString(env, profileStateStr)); + + switch (info->profileClass) { + case ES10C_PROFILE_CLASS_TEST: + profileClassStr = "test"; + break; + case ES10C_PROFILE_CLASS_PROVISIONING: + profileClassStr = "provisioning"; + break; + case ES10C_PROFILE_CLASS_OPERATIONAL: + profileClassStr = "operational"; + break; + default: + profileClassStr = "unknown"; + break; + } class = (*env)->CallStaticObjectMethod(env, local_profile_class_class, local_profile_class_from_string, - toJString(env, info->profileClass)); + toJString(env, profileClassStr)); jinfo = (*env)->NewObject(env, local_profile_info_class, local_profile_info_constructor, iccid, state, name, nickName, serviceProvider, isdpAid, class); @@ -178,8 +209,8 @@ jobject profile_info_native_to_java(JNIEnv *env, struct es10c_profile_info *info JNIEXPORT jobjectArray JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cGetProfilesInfo(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10c_profile_info *info = NULL; - struct es10c_profile_info *curr = NULL; + struct es10c_profile_info_list *info = NULL; + struct es10c_profile_info_list *curr = NULL; jobjectArray ret = NULL; jobject jinfo = NULL; int count = 0; @@ -211,7 +242,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cEnableProfile(JNIEnv *env, jobject thiz int ret; _iccid = (*env)->GetStringUTFChars(env, iccid, NULL); - ret = es10c_enable_profile_iccid(ctx, _iccid, 1); + ret = es10c_enable_profile(ctx, _iccid, 1); (*env)->ReleaseStringUTFChars(env, iccid, _iccid); return ret; } @@ -224,7 +255,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDisableProfile(JNIEnv *env, jobject thi int ret; _iccid = (*env)->GetStringUTFChars(env, iccid, NULL); - ret = es10c_disable_profile_iccid(ctx, _iccid, 1); + ret = es10c_disable_profile(ctx, _iccid, 1); (*env)->ReleaseStringUTFChars(env, iccid, _iccid); return ret; } @@ -253,7 +284,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDeleteProfile(JNIEnv *env, jobject thiz int ret; _iccid = (*env)->GetStringUTFChars(env, iccid, NULL); - ret = es10c_delete_profile_iccid(ctx, _iccid); + ret = es10c_delete_profile(ctx, _iccid); (*env)->ReleaseStringUTFChars(env, iccid, _iccid); return ret; } @@ -261,7 +292,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDeleteProfile(JNIEnv *env, jobject thiz JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10cex_euiccinfo2 * info; + struct es10c_ex_euiccinfo2 info = { 0 }; jobjectArray euiccCiPKIdListForVerification = NULL; jobjectArray euiccCiPKIdListForSigning = NULL; jstring sas_accreditation_number = NULL; @@ -273,24 +304,24 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th char **curr = NULL; int count = 0; - if (es10cex_get_euiccinfo2(ctx, &info) < 0) + if (es10c_ex_get_euiccinfo2(ctx, &info) < 0) goto out; - profile_version = toJString(env, info->profileVersion); - euicc_firmware_version = toJString(env, info->euiccFirmwareVer); - global_platform_version = toJString(env, info->globalplatformVersion); - sas_accreditation_number = toJString(env, info->sasAcreditationNumber); - pp_version = toJString(env, info->ppVersion); + profile_version = toJString(env, info.profileVersion); + euicc_firmware_version = toJString(env, info.euiccFirmwareVer); + global_platform_version = toJString(env, info.globalplatformVersion); + sas_accreditation_number = toJString(env, info.sasAcreditationNumber); + pp_version = toJString(env, info.ppVersion); - count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForSigning, curr); + count = LPAC_JNI_NULL_TERM_LIST_COUNT(info.euiccCiPKIdListForSigning, curr); euiccCiPKIdListForSigning = (*env)->NewObjectArray(env, count, string_class, NULL); - LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForSigning, curr, { + LPAC_JNI_NULL_TERM_LIST_FOREACH(info.euiccCiPKIdListForSigning, curr, { (*env)->SetObjectArrayElement(env, euiccCiPKIdListForSigning, i, toJString(env, *curr)); }); - count = LPAC_JNI_NULL_TERM_LIST_COUNT(info->euiccCiPKIdListForVerification, curr); + count = LPAC_JNI_NULL_TERM_LIST_COUNT(info.euiccCiPKIdListForVerification, curr); euiccCiPKIdListForVerification = (*env)->NewObjectArray(env, count, string_class, NULL); - LPAC_JNI_NULL_TERM_LIST_FOREACH(info->euiccCiPKIdListForVerification, curr, { + LPAC_JNI_NULL_TERM_LIST_FOREACH(info.euiccCiPKIdListForVerification, curr, { (*env)->SetObjectArrayElement(env, euiccCiPKIdListForVerification, i, toJString(env, *curr)); }); @@ -298,8 +329,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th profile_version, euicc_firmware_version, global_platform_version, sas_accreditation_number, pp_version, - info->extCardResource.freeNonVolatileMemory, - info->extCardResource.freeVolatileMemory, + info.extCardResource.freeNonVolatileMemory, + info.extCardResource.freeVolatileMemory, euiccCiPKIdListForSigning, euiccCiPKIdListForVerification); @@ -309,7 +340,6 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th (*env)->DeleteLocalRef(env, global_platform_version); (*env)->DeleteLocalRef(env, sas_accreditation_number); (*env)->DeleteLocalRef(env, pp_version); - if (info != NULL) - es10cex_free_euiccinfo2(info); + es10c_ex_euiccinfo2_free(&info); return ret; } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index 86255de..47cb9f3 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -33,8 +33,9 @@ void lpac_notifications_init() { JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10b_notification_metadata *info = NULL; - struct es10b_notification_metadata *curr = NULL; + struct es10b_notification_metadata_list *info = NULL; + struct es10b_notification_metadata_list *curr = NULL; + const char *profileManagementOperationStr = NULL; jobject notification = NULL; jobject operation = NULL; jobjectArray ret = NULL; @@ -48,10 +49,27 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject t ret = (*env)->NewObjectArray(env, count, local_profile_notification_class, NULL); LPAC_JNI_LINKED_LIST_FOREACH(info, curr, { + switch (curr->profileManagementOperation) { + case ES10B_PROFILE_MANAGEMENT_OPERATION_INSTALL: + profileManagementOperationStr = "install"; + break; + case ES10B_PROFILE_MANAGEMENT_OPERATION_DELETE: + profileManagementOperationStr = "delete"; + break; + case ES10B_PROFILE_MANAGEMENT_OPERATION_ENABLE: + profileManagementOperationStr = "enable"; + break; + case ES10B_PROFILE_MANAGEMENT_OPERATION_DISABLE: + profileManagementOperationStr = "disable"; + break; + default: + profileManagementOperationStr = "unknown"; + } + operation = (*env)->CallStaticObjectMethod(env, local_profile_notification_operation_class, local_profile_notification_operation_from_string, - toJString(env, curr->profileManagementOperation)); + toJString(env, profileManagementOperationStr)); notification = (*env)->NewObject(env, local_profile_notification_class, @@ -73,15 +91,19 @@ JNIEXPORT jint JNICALL Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz, jlong handle, jlong seq_number) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10b_notification notification; + struct es9p_ctx es9p_ctx = { 0 }; + struct es10b_pending_notification notification; int res; - res = es10b_retrieve_notification(ctx, ¬ification, (unsigned long) seq_number); + res = es10b_retrieve_notifications_list(ctx, ¬ification, (unsigned long) seq_number); syslog(LOG_DEBUG, "es10b_retrieve_notification = %d", res); if (res < 0) goto out; - res = es9p_handle_notification(ctx, notification.receiver, notification.b64_payload); + es9p_ctx.euicc_ctx = ctx; + es9p_ctx.address = notification.notificationAddress; + + res = es9p_handle_notification(&es9p_ctx, notification.b64_PendingNotification); syslog(LOG_DEBUG, "es9p_handle_notification = %d", res); if (res < 0) goto out; From 4ded234ed2d668a6fc80bd47e1a6f4ffdb2fa9ba Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 24 Feb 2024 16:09:57 -0500 Subject: [PATCH 014/472] lpac-jni: Reformat JNI C code --- .../src/main/jni/lpac-jni/interface-wrapper.c | 30 ++++++--- .../src/main/jni/lpac-jni/interface-wrapper.h | 1 + .../src/main/jni/lpac-jni/lpac-download.c | 67 +++++++++++++------ .../src/main/jni/lpac-jni/lpac-download.h | 1 + .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 27 +++++--- .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.h | 1 + .../main/jni/lpac-jni/lpac-notifications.c | 7 +- 7 files changed, 92 insertions(+), 42 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c index 85c9f16..21b7ba4 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -18,8 +18,10 @@ void interface_wrapper_init() { jclass apdu_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ApduInterface"); method_apdu_connect = (*env)->GetMethodID(env, apdu_class, "connect", "()V"); method_apdu_disconnect = (*env)->GetMethodID(env, apdu_class, "disconnect", "()V"); - method_apdu_logical_channel_open = (*env)->GetMethodID(env, apdu_class, "logicalChannelOpen", "([B)I"); - method_apdu_logical_channel_close = (*env)->GetMethodID(env, apdu_class, "logicalChannelClose", "(I)V"); + method_apdu_logical_channel_open = (*env)->GetMethodID(env, apdu_class, "logicalChannelOpen", + "([B)I"); + method_apdu_logical_channel_close = (*env)->GetMethodID(env, apdu_class, "logicalChannelClose", + "(I)V"); method_apdu_transmit = (*env)->GetMethodID(env, apdu_class, "transmit", "([B)[B"); jclass http_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface"); @@ -43,26 +45,32 @@ static void apdu_interface_disconnect(struct euicc_ctx *ctx) { (*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, method_apdu_disconnect); } -static int apdu_interface_logical_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len) { +static int +apdu_interface_logical_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len) { LPAC_JNI_SETUP_ENV; jbyteArray jbarr = (*env)->NewByteArray(env, aid_len); (*env)->SetByteArrayRegion(env, jbarr, 0, aid_len, (const jbyte *) aid); - jint ret = (*env)->CallIntMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, method_apdu_logical_channel_open, jbarr); + jint ret = (*env)->CallIntMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, + method_apdu_logical_channel_open, jbarr); LPAC_JNI_EXCEPTION_RETURN; return ret; } static void apdu_interface_logical_channel_close(struct euicc_ctx *ctx, uint8_t channel) { LPAC_JNI_SETUP_ENV; - (*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, method_apdu_logical_channel_close, channel); + (*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, + method_apdu_logical_channel_close, channel); (*env)->ExceptionClear(env); } -static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) { +static int +apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, + uint32_t tx_len) { LPAC_JNI_SETUP_ENV; jbyteArray txArr = (*env)->NewByteArray(env, tx_len); (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx); - jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, method_apdu_transmit, txArr); + jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, + method_apdu_transmit, txArr); LPAC_JNI_EXCEPTION_RETURN; *rx_len = (*env)->GetArrayLength(env, ret); *rx = malloc(*rx_len * sizeof(uint8_t)); @@ -72,7 +80,10 @@ static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t return 0; } -static int http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len, const char **headers) { +static int +http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, uint8_t **rx, + uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len, + const char **headers) { LPAC_JNI_SETUP_ENV; jstring jurl = toJString(env, url); jbyteArray txArr = (*env)->NewByteArray(env, tx_len); @@ -89,7 +100,8 @@ static int http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint3 (*env)->DeleteLocalRef(env, header); } - jobject ret = (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->http_interface, method_http_transmit, jurl, txArr, headersArr); + jobject ret = (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->http_interface, + method_http_transmit, jurl, txArr, headersArr); LPAC_JNI_EXCEPTION_RETURN; *rcode = (*env)->GetIntField(env, ret, field_resp_rcode); jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h index acfdc33..e22837b 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h @@ -1,5 +1,6 @@ #pragma once #undef NDEBUG + #include #include #include "lpac-jni.h" diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index 5674128..6409d94 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -15,25 +15,44 @@ jmethodID on_state_update; void lpac_download_init() { LPAC_JNI_SETUP_ENV; - jclass download_state_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState"); - jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class, "Preparing", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); - download_state_preparing = (*env)->GetStaticObjectField(env, download_state_class, download_state_preparing_field); + jclass download_state_class = (*env)->FindClass(env, + "net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState"); + jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class, + "Preparing", + "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); + download_state_preparing = (*env)->GetStaticObjectField(env, download_state_class, + download_state_preparing_field); download_state_preparing = (*env)->NewGlobalRef(env, download_state_preparing); - jfieldID download_state_connecting_field = (*env)->GetStaticFieldID(env, download_state_class, "Connecting", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); - download_state_connecting = (*env)->GetStaticObjectField(env, download_state_class, download_state_connecting_field); + jfieldID download_state_connecting_field = (*env)->GetStaticFieldID(env, download_state_class, + "Connecting", + "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); + download_state_connecting = (*env)->GetStaticObjectField(env, download_state_class, + download_state_connecting_field); download_state_connecting = (*env)->NewGlobalRef(env, download_state_connecting); - jfieldID download_state_authenticating_field = (*env)->GetStaticFieldID(env, download_state_class, "Authenticating", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); - download_state_authenticating = (*env)->GetStaticObjectField(env, download_state_class, download_state_authenticating_field); + jfieldID download_state_authenticating_field = (*env)->GetStaticFieldID(env, + download_state_class, + "Authenticating", + "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); + download_state_authenticating = (*env)->GetStaticObjectField(env, download_state_class, + download_state_authenticating_field); download_state_authenticating = (*env)->NewGlobalRef(env, download_state_authenticating); - jfieldID download_state_downloading_field = (*env)->GetStaticFieldID(env, download_state_class, "Downloading", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); - download_state_downloading = (*env)->GetStaticObjectField(env, download_state_class, download_state_downloading_field); + jfieldID download_state_downloading_field = (*env)->GetStaticFieldID(env, download_state_class, + "Downloading", + "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); + download_state_downloading = (*env)->GetStaticObjectField(env, download_state_class, + download_state_downloading_field); download_state_downloading = (*env)->NewGlobalRef(env, download_state_downloading); - jfieldID download_state_finalizng_field = (*env)->GetStaticFieldID(env, download_state_class, "Finalizing", "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); - download_state_finalizing = (*env)->GetStaticObjectField(env, download_state_class, download_state_finalizng_field); + jfieldID download_state_finalizng_field = (*env)->GetStaticFieldID(env, download_state_class, + "Finalizing", + "Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;"); + download_state_finalizing = (*env)->GetStaticObjectField(env, download_state_class, + download_state_finalizng_field); download_state_finalizing = (*env)->NewGlobalRef(env, download_state_finalizing); - jclass download_callback_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ProfileDownloadCallback"); - on_state_update = (*env)->GetMethodID(env, download_callback_class, "onStateUpdate", "(Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;)V"); + jclass download_callback_class = (*env)->FindClass(env, + "net/typeblog/lpac_jni/ProfileDownloadCallback"); + on_state_update = (*env)->GetMethodID(env, download_callback_class, "onStateUpdate", + "(Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;)V"); } JNIEXPORT jint JNICALL @@ -42,7 +61,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j jstring imei, jstring confirmation_code, jobject callback) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es9p_ctx es9p_ctx = { 0 }; + struct es9p_ctx es9p_ctx = {0}; struct es10b_load_bound_profile_package_result es10b_load_bound_profile_package_result; struct es10b_authenticate_server_param es10b_authenticate_server_param; struct es10b_prepare_download_param es10b_prepare_download_param; @@ -79,7 +98,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_connecting); - ret = es9p_initiate_authentication(&es9p_ctx, &es10b_authenticate_server_param, b64_euicc_challenge, b64_euicc_info_1); + ret = es9p_initiate_authentication(&es9p_ctx, &es10b_authenticate_server_param, + b64_euicc_challenge, b64_euicc_info_1); if (ret < 0) goto out; @@ -87,30 +107,35 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j es10b_authenticate_server_param.imei = _imei; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_authenticating); - ret = es10b_authenticate_server(ctx, &b64_authenticate_server_response, &es10b_authenticate_server_param); + ret = es10b_authenticate_server(ctx, &b64_authenticate_server_response, + &es10b_authenticate_server_param); if (ret < 0) goto out; - ret = es9p_authenticate_client(&es9p_ctx, &es10b_prepare_download_param, b64_authenticate_server_response); + ret = es9p_authenticate_client(&es9p_ctx, &es10b_prepare_download_param, + b64_authenticate_server_response); if (ret < 0) goto out; es10b_prepare_download_param.confirmationCode = _confirmation_code; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading); - ret = es10b_prepare_download(ctx, &b64_prepare_download_response, &es10b_prepare_download_param); + ret = es10b_prepare_download(ctx, &b64_prepare_download_response, + &es10b_prepare_download_param); if (ret < 0) goto out; - ret = es9p_get_bound_profile_package(&es9p_ctx, &b64_bound_profile_package, b64_prepare_download_response); + ret = es9p_get_bound_profile_package(&es9p_ctx, &b64_bound_profile_package, + b64_prepare_download_response); if (ret < 0) goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_finalizing); // TODO: Expose error code as Java-side exceptions? - ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result, b64_bound_profile_package); + ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result, + b64_bound_profile_package); -out: + out: free(b64_authenticate_server_response); free(b64_prepare_download_response); free(b64_euicc_info_1); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h index eb99767..7130f52 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.h @@ -1,4 +1,5 @@ #pragma once + #include #include "lpac-jni.h" diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index dca47f4..99040fc 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -10,7 +10,7 @@ #include "lpac-notifications.h" #include "interface-wrapper.h" -JavaVM *jvm = NULL; +JavaVM *jvm = NULL; jclass local_profile_info_class; jmethodID local_profile_info_constructor; @@ -38,24 +38,32 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { LPAC_JNI_SETUP_ENV; string_class = (*env)->FindClass(env, "java/lang/String"); string_class = (*env)->NewGlobalRef(env, string_class); - string_constructor = (*env)->GetMethodID(env, string_class, "", "([BLjava/lang/String;)V"); + string_constructor = (*env)->GetMethodID(env, string_class, "", + "([BLjava/lang/String;)V"); local_profile_info_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileInfo"); local_profile_info_class = (*env)->NewGlobalRef(env, local_profile_info_class); local_profile_info_constructor = (*env)->GetMethodID(env, local_profile_info_class, "", "(Ljava/lang/String;Lnet/typeblog/lpac_jni/LocalProfileInfo$State;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lnet/typeblog/lpac_jni/LocalProfileInfo$Clazz;)V"); - local_profile_state_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileInfo$State"); + local_profile_state_class = (*env)->FindClass(env, + "net/typeblog/lpac_jni/LocalProfileInfo$State"); local_profile_state_class = (*env)->NewGlobalRef(env, local_profile_state_class); - local_profile_state_from_string = (*env)->GetStaticMethodID(env, local_profile_state_class, "fromString", "(Ljava/lang/String;)Lnet/typeblog/lpac_jni/LocalProfileInfo$State;"); + local_profile_state_from_string = (*env)->GetStaticMethodID(env, local_profile_state_class, + "fromString", + "(Ljava/lang/String;)Lnet/typeblog/lpac_jni/LocalProfileInfo$State;"); - local_profile_class_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileInfo$Clazz"); + local_profile_class_class = (*env)->FindClass(env, + "net/typeblog/lpac_jni/LocalProfileInfo$Clazz"); local_profile_class_class = (*env)->NewGlobalRef(env, local_profile_class_class); - local_profile_class_from_string = (*env)->GetStaticMethodID(env, local_profile_class_class, "fromString", "(Ljava/lang/String;)Lnet/typeblog/lpac_jni/LocalProfileInfo$Clazz;"); + local_profile_class_from_string = (*env)->GetStaticMethodID(env, local_profile_class_class, + "fromString", + "(Ljava/lang/String;)Lnet/typeblog/lpac_jni/LocalProfileInfo$Clazz;"); euicc_info2_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/EuiccInfo2"); euicc_info2_class = (*env)->NewGlobalRef(env, euicc_info2_class); - euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[Ljava/lang/String;[Ljava/lang/String;)V"); + euicc_info2_constructor = (*env)->GetMethodID(env, euicc_info2_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[Ljava/lang/String;[Ljava/lang/String;)V"); const char _unused[1]; empty_string = (*env)->NewString(env, _unused, 0); @@ -292,7 +300,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDeleteProfile(JNIEnv *env, jobject thiz JNIEXPORT jobject JNICALL Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject thiz, jlong handle) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es10c_ex_euiccinfo2 info = { 0 }; + struct es10c_ex_euiccinfo2 info = {0}; jobjectArray euiccCiPKIdListForVerification = NULL; jobjectArray euiccCiPKIdListForSigning = NULL; jstring sas_accreditation_number = NULL; @@ -322,7 +330,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cexGetEuiccInfo2(JNIEnv *env, jobject th count = LPAC_JNI_NULL_TERM_LIST_COUNT(info.euiccCiPKIdListForVerification, curr); euiccCiPKIdListForVerification = (*env)->NewObjectArray(env, count, string_class, NULL); LPAC_JNI_NULL_TERM_LIST_FOREACH(info.euiccCiPKIdListForVerification, curr, { - (*env)->SetObjectArrayElement(env, euiccCiPKIdListForVerification, i, toJString(env, *curr)); + (*env)->SetObjectArrayElement(env, euiccCiPKIdListForVerification, i, + toJString(env, *curr)); }); ret = (*env)->NewObject(env, euicc_info2_class, euicc_info2_constructor, diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h index e60c465..6895aef 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h @@ -1,4 +1,5 @@ #pragma once + #include #include #include diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index 47cb9f3..c544cc4 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -19,7 +19,7 @@ void lpac_notifications_init() { (*env)->NewGlobalRef(env, local_profile_notification_class); local_profile_notification_constructor = (*env)->GetMethodID(env, local_profile_notification_class, "", - "(JLnet/typeblog/lpac_jni/LocalProfileNotification$Operation;Ljava/lang/String;Ljava/lang/String;)V"); + "(JLnet/typeblog/lpac_jni/LocalProfileNotification$Operation;Ljava/lang/String;Ljava/lang/String;)V"); local_profile_notification_operation_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/LocalProfileNotification$Operation"); @@ -73,7 +73,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject t notification = (*env)->NewObject(env, local_profile_notification_class, - local_profile_notification_constructor, curr->seqNumber, operation, + local_profile_notification_constructor, curr->seqNumber, + operation, toJString(env, curr->notificationAddress), toJString(env, curr->iccid)); @@ -91,7 +92,7 @@ JNIEXPORT jint JNICALL Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz, jlong handle, jlong seq_number) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es9p_ctx es9p_ctx = { 0 }; + struct es9p_ctx es9p_ctx = {0}; struct es10b_pending_notification notification; int res; From 18cd9acdb8ed77e8b19641f7356da632cfeec3e0 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 24 Feb 2024 16:10:55 -0500 Subject: [PATCH 015/472] lpac-jni: Call es9p_ctx_free --- libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c | 1 + libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c | 1 + 2 files changed, 2 insertions(+) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index 6409d94..eb90e38 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -136,6 +136,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j b64_bound_profile_package); out: + es9p_ctx_free(&es9p_ctx); free(b64_authenticate_server_response); free(b64_prepare_download_response); free(b64_euicc_info_1); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index c544cc4..b15cba2 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -110,6 +110,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz goto out; out: + es9p_ctx_free(&es9p_ctx); return res; } From 2a8fb99ed02a4beaa15928aed4c220d6039182b5 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 24 Feb 2024 16:18:15 -0500 Subject: [PATCH 016/472] lpac-jni: Assert jlong is enough to hold a platform sized pointer --- libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h index 6895aef..1915c5e 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h @@ -4,6 +4,9 @@ #include #include +_Static_assert(sizeof(void *) <= sizeof(jlong), + "jlong must be big enough to hold a platform raw pointer"); + struct lpac_jni_ctx { jobject apdu_interface; jobject http_interface; From 19c63113a19478c4f4cb4247a0dc438feb199408 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 25 Feb 2024 13:28:59 -0500 Subject: [PATCH 017/472] ProfileDeleteFragment: Require confirmation via inputting profile name --- .../im/angry/openeuicc/ui/ProfileDeleteFragment.kt | 14 +++++++++++++- app-common/src/main/res/values/strings.xml | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt index 361931a..ede48ea 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt @@ -2,7 +2,9 @@ package im.angry.openeuicc.ui import android.app.Dialog import android.os.Bundle +import android.text.Editable import android.util.Log +import android.widget.EditText import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope @@ -26,11 +28,21 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker { } } + private val editText by lazy { + EditText(requireContext()).apply { + hint = Editable.Factory.getInstance().newEditable( + getString(R.string.profile_delete_confirm_input, requireArguments().getString("name")!!) + ) + } + } + private val inputMatchesName: Boolean + get() = editText.text.toString() == requireArguments().getString("name")!! private var deleting = false override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme).apply { setMessage(getString(R.string.profile_delete_confirm, requireArguments().getString("name"))) + setView(editText) setPositiveButton(android.R.string.ok, null) // Set listener to null to prevent auto closing setNegativeButton(android.R.string.cancel, null) }.create() @@ -40,7 +52,7 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker { super.onResume() val alertDialog = dialog!! as AlertDialog alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - if (!deleting) delete() + if (!deleting && inputMatchesName) delete() } alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { if (!deleting) dismiss() diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 96c7bf3..9d8932a 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ New nickname Are you sure you want to delete the profile %s? This operation is irreversible. + Type \'%s\' here to confirm deletion Profile Notifications Manage Notifications From 412fd3147763242483860f5ba36677d373003968 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 25 Feb 2024 14:57:52 -0500 Subject: [PATCH 018/472] lpac-jni: Uprev lpac libeuicc * use `-z muldefs` temporarily to work around upstream bug. --- libs/lpac-jni/src/main/jni/Application.mk | 2 +- libs/lpac-jni/src/main/jni/lpac | 2 +- .../src/main/jni/lpac-jni/lpac-download.c | 54 ++++++------------- .../lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 6 +-- .../main/jni/lpac-jni/lpac-notifications.c | 12 ++--- 5 files changed, 25 insertions(+), 51 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/Application.mk b/libs/lpac-jni/src/main/jni/Application.mk index 173c76f..2112196 100644 --- a/libs/lpac-jni/src/main/jni/Application.mk +++ b/libs/lpac-jni/src/main/jni/Application.mk @@ -1,4 +1,4 @@ APP_ABI := all APP_SHORT_COMMANDS := true APP_CFLAGS := -Wno-compound-token-split-by-macro -APP_LDFLAGS := -Wl,--build-id=none \ No newline at end of file +APP_LDFLAGS := -Wl,--build-id=none -z muldefs \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac b/libs/lpac-jni/src/main/jni/lpac index d06a2a0..47f44f9 160000 --- a/libs/lpac-jni/src/main/jni/lpac +++ b/libs/lpac-jni/src/main/jni/lpac @@ -1 +1 @@ -Subproject commit d06a2a0e1f9bd29d86bc4f0881a84f67cd708aa3 +Subproject commit 47f44f911099bffdbdb4854500578ba18ab19d06 diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index eb90e38..f8b27ff 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "lpac-download.h" jobject download_state_preparing; @@ -61,16 +62,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j jstring imei, jstring confirmation_code, jobject callback) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es9p_ctx es9p_ctx = {0}; struct es10b_load_bound_profile_package_result es10b_load_bound_profile_package_result; - struct es10b_authenticate_server_param es10b_authenticate_server_param; - struct es10b_prepare_download_param es10b_prepare_download_param; - char *b64_authenticate_server_response = NULL; - char *b64_prepare_download_response = NULL; - char *b64_bound_profile_package = NULL; - char *b64_euicc_challenge = NULL; - char *b64_euicc_info_1 = NULL; - char *transaction_id = NULL; const char *_confirmation_code = NULL; const char *_matching_id = NULL; const char *_smdp = NULL; @@ -85,63 +77,47 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j if (imei != NULL) _imei = (*env)->GetStringUTFChars(env, imei, NULL); - es9p_ctx.euicc_ctx = ctx; - es9p_ctx.address = _smdp; + ctx->http.server_address = _smdp; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_preparing); - ret = es10b_get_euicc_challenge(ctx, &b64_euicc_challenge); - if (ret < 0) - goto out; - - ret = es10b_get_euicc_info(ctx, &b64_euicc_info_1); + ret = es10b_get_euicc_challenge_and_info(ctx); + syslog(LOG_INFO, "es10b_get_euicc_challenge_and_info %d", ret); if (ret < 0) goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_connecting); - ret = es9p_initiate_authentication(&es9p_ctx, &es10b_authenticate_server_param, - b64_euicc_challenge, b64_euicc_info_1); + ret = es9p_initiate_authentication(ctx); + syslog(LOG_INFO, "es9p_initiate_authentication %d", ret); if (ret < 0) goto out; - es10b_authenticate_server_param.matchingId = _matching_id; - es10b_authenticate_server_param.imei = _imei; - (*env)->CallVoidMethod(env, callback, on_state_update, download_state_authenticating); - ret = es10b_authenticate_server(ctx, &b64_authenticate_server_response, - &es10b_authenticate_server_param); + ret = es10b_authenticate_server(ctx, _matching_id, _imei); + syslog(LOG_INFO, "es10b_authenticate_server %d", ret); if (ret < 0) goto out; - ret = es9p_authenticate_client(&es9p_ctx, &es10b_prepare_download_param, - b64_authenticate_server_response); + ret = es9p_authenticate_client(ctx); if (ret < 0) goto out; - es10b_prepare_download_param.confirmationCode = _confirmation_code; - (*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading); - ret = es10b_prepare_download(ctx, &b64_prepare_download_response, - &es10b_prepare_download_param); + ret = es10b_prepare_download(ctx, _confirmation_code); + syslog(LOG_INFO, "es10b_prepare_download %d", ret); if (ret < 0) goto out; - ret = es9p_get_bound_profile_package(&es9p_ctx, &b64_bound_profile_package, - b64_prepare_download_response); + ret = es9p_get_bound_profile_package(ctx); if (ret < 0) goto out; (*env)->CallVoidMethod(env, callback, on_state_update, download_state_finalizing); // TODO: Expose error code as Java-side exceptions? - ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result, - b64_bound_profile_package); + ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result); + syslog(LOG_INFO, "es10b_load_bound_profile_package %d", ret); out: - es9p_ctx_free(&es9p_ctx); - free(b64_authenticate_server_response); - free(b64_prepare_download_response); - free(b64_euicc_info_1); - free(b64_euicc_challenge); - free(transaction_id); + euicc_http_cleanup(ctx); if (_confirmation_code != NULL) (*env)->ReleaseStringUTFChars(env, confirmation_code, _confirmation_code); if (_matching_id != NULL) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index 99040fc..eddb659 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -81,10 +81,10 @@ Java_net_typeblog_lpac_1jni_LpacJni_createContext(JNIEnv *env, jobject thiz, ctx = malloc(sizeof(struct euicc_ctx)); jni_ctx = malloc(sizeof(struct lpac_jni_ctx)); - memset(ctx, 0, sizeof(struct lpac_jni_ctx)); + memset(ctx, 0, sizeof(struct euicc_ctx)); memset(jni_ctx, 0, sizeof(struct lpac_jni_ctx)); - ctx->interface.apdu = &lpac_jni_apdu_interface; - ctx->interface.http = &lpac_jni_http_interface; + ctx->apdu.interface = &lpac_jni_apdu_interface; + ctx->http.interface = &lpac_jni_http_interface; jni_ctx->apdu_interface = (*env)->NewGlobalRef(env, apdu_interface); jni_ctx->http_interface = (*env)->NewGlobalRef(env, http_interface); ctx->userdata = (void *) jni_ctx; diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c index b15cba2..1a5ac7c 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-notifications.c @@ -84,7 +84,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10bListNotification(JNIEnv *env, jobject t (*env)->DeleteLocalRef(env, notification); }); - es10b_notification_metadata_free_all(info); + es10b_notification_metadata_list_free_all(info); return ret; } @@ -92,25 +92,23 @@ JNIEXPORT jint JNICALL Java_net_typeblog_lpac_1jni_LpacJni_handleNotification(JNIEnv *env, jobject thiz, jlong handle, jlong seq_number) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; - struct es9p_ctx es9p_ctx = {0}; struct es10b_pending_notification notification; int res; res = es10b_retrieve_notifications_list(ctx, ¬ification, (unsigned long) seq_number); - syslog(LOG_DEBUG, "es10b_retrieve_notification = %d", res); + syslog(LOG_DEBUG, "es10b_retrieve_notification = %d %s", res, notification.b64_PendingNotification); if (res < 0) goto out; - es9p_ctx.euicc_ctx = ctx; - es9p_ctx.address = notification.notificationAddress; + ctx->http.server_address = notification.notificationAddress; - res = es9p_handle_notification(&es9p_ctx, notification.b64_PendingNotification); + res = es9p_handle_notification(ctx, notification.b64_PendingNotification); syslog(LOG_DEBUG, "es9p_handle_notification = %d", res); if (res < 0) goto out; out: - es9p_ctx_free(&es9p_ctx); + euicc_http_cleanup(ctx); return res; } From 12d02ee76c1c2373199dd714cfb3962e023bbe94 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 25 Feb 2024 15:00:24 -0500 Subject: [PATCH 019/472] lpac-jni: malloc -> calloc --- libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c | 4 ++-- libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c index 21b7ba4..9059171 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -73,7 +73,7 @@ apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, c method_apdu_transmit, txArr); LPAC_JNI_EXCEPTION_RETURN; *rx_len = (*env)->GetArrayLength(env, ret); - *rx = malloc(*rx_len * sizeof(uint8_t)); + *rx = calloc(*rx_len, sizeof(uint8_t)); (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, ret); @@ -106,7 +106,7 @@ http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, *rcode = (*env)->GetIntField(env, ret, field_resp_rcode); jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data); *rx_len = (*env)->GetArrayLength(env, rxArr); - *rx = malloc(*rx_len * sizeof(uint8_t)); + *rx = calloc(*rx_len, sizeof(uint8_t)); (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, rxArr); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index eddb659..6cdda00 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -79,10 +79,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_createContext(JNIEnv *env, jobject thiz, struct lpac_jni_ctx *jni_ctx = NULL; struct euicc_ctx *ctx = NULL; - ctx = malloc(sizeof(struct euicc_ctx)); - jni_ctx = malloc(sizeof(struct lpac_jni_ctx)); - memset(ctx, 0, sizeof(struct euicc_ctx)); - memset(jni_ctx, 0, sizeof(struct lpac_jni_ctx)); + ctx = calloc(1, sizeof(struct euicc_ctx)); + jni_ctx = calloc(1, sizeof(struct lpac_jni_ctx)); ctx->apdu.interface = &lpac_jni_apdu_interface; ctx->http.interface = &lpac_jni_http_interface; jni_ctx->apdu_interface = (*env)->NewGlobalRef(env, apdu_interface); From 6d200d14acce00a877c0cfa24b1bf4ae9c42a2ab Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 27 Feb 2024 19:50:29 -0500 Subject: [PATCH 020/472] nuke asn1c in Android.bp and Android.mk --- libs/lpac-jni/src/main/jni/Android.bp | 12 ------------ libs/lpac-jni/src/main/jni/Android.mk | 12 +----------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/Android.bp b/libs/lpac-jni/src/main/jni/Android.bp index c710a16..ce622de 100644 --- a/libs/lpac-jni/src/main/jni/Android.bp +++ b/libs/lpac-jni/src/main/jni/Android.bp @@ -13,16 +13,6 @@ cc_library_static { ], } -cc_library_static { - name: "liblpac-asn1c", - defaults: ["lpac-jni-defaults"], - local_include_dirs: ["lpac/euicc/asn1c"], - cflags: ["-DHAVE_CONFIG_H"], - srcs: [ - "lpac/euicc/asn1c/asn1/*.c", - ], -} - cc_library_static { name: "liblpac-euicc", defaults: ["lpac-jni-defaults"], @@ -30,7 +20,6 @@ cc_library_static { "lpac/euicc/*.c", ], static_libs: [ - "liblpac-asn1c", "liblpac-cjson", ], } @@ -44,7 +33,6 @@ cc_library_shared { ], static_libs: [ "liblpac-euicc", - "liblpac-asn1c", "liblpac-cjson", ], shared_libs: ["liblog"], diff --git a/libs/lpac-jni/src/main/jni/Android.mk b/libs/lpac-jni/src/main/jni/Android.mk index 0a216b3..c0bcee7 100644 --- a/libs/lpac-jni/src/main/jni/Android.mk +++ b/libs/lpac-jni/src/main/jni/Android.mk @@ -14,20 +14,10 @@ LOCAL_SRC_FILES := \ $(call all-c-files-under, lpac/cjson) include $(BUILD_STATIC_LIBRARY) -include $(CLEAR_VARS) -# libasn1c, the ASN parser component from lpac -LOCAL_MODULE := lpac-asn1c -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/lpac/euicc/asn1c -LOCAL_SRC_FILES := \ - $(call all-c-files-under, lpac/euicc/asn1c/asn1) -LOCAL_CFLAGS := -DHAVE_CONFIG_H -include $(BUILD_STATIC_LIBRARY) - include $(CLEAR_VARS) # libeuicc component from lpac, which contains the actual implementation LOCAL_MODULE := lpac-euicc -LOCAL_STATIC_LIBRARIES := lpac-asn1c lpac-cjson +LOCAL_STATIC_LIBRARIES := lpac-cjson LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/lpac LOCAL_SRC_FILES := \ From a1b264362509d0d8bc358101719f8251cca43b5e Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 27 Feb 2024 20:06:08 -0500 Subject: [PATCH 021/472] lpac-jni: Always add GSMA ROOT CI1 --- .../main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt index 84fa227..f3e5e90 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -24,8 +24,9 @@ internal fun keyIdToKeystore(keyIds: Array): KeyStore { } } - // If no known certs have been added, add at least the default GSMA CI - if (ret.size() == 0) { + // At the very least, we should always have GSMA ROOT CI1 trusted + // many servers supporting custom roots are served with GSMA ROOT CI1 for TLS + if (!ret.isCertificateEntry(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1)) { getCertificate(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1)?.let { cert -> ret.setCertificateEntry(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1, cert) } From 62e3e41c52ee224f41e63865c22f257e314520c2 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 28 Feb 2024 19:57:47 -0500 Subject: [PATCH 022/472] lpac-jni: Mark network-related LPA methods as synchronized These methods rely on non-thread-safe internal states within lpac's context. --- .../net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 072fcaa..b81a41a 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -115,6 +115,7 @@ class LocalProfileAssistantImpl( return LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0 } + @Synchronized override fun downloadProfile(smdp: String, matchingId: String?, imei: String?, confirmationCode: String?, callback: ProfileDownloadCallback): Boolean { return LpacJni.downloadProfile(contextHandle, smdp, matchingId, imei, confirmationCode, callback) == 0 @@ -123,6 +124,7 @@ class LocalProfileAssistantImpl( override fun deleteNotification(seqNumber: Long): Boolean = LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0 + @Synchronized override fun handleNotification(seqNumber: Long): Boolean = LpacJni.handleNotification(contextHandle, seqNumber).also { Log.d(TAG, "handleNotification $seqNumber = $it") From c0d1c29b7fcd21803e8174fdf522fbda215fd249 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 3 Mar 2024 10:55:35 -0500 Subject: [PATCH 023/472] feat: Show error logs on crash when unprivileged ...however, don't do this in privileged mode because OpenEuiccService is supposed to be background, and we don't want to just randomly show up when things go wrong. --- .../im/angry/openeuicc/ui/LogsActivity.kt | 12 ++-------- .../java/im/angry/openeuicc/util/Utils.kt | 11 +++++++++ app-unpriv/src/main/AndroidManifest.xml | 2 +- .../UnprivilegedOpenEuiccApplication.kt | 23 +++++++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 app-unpriv/src/main/java/im/angry/openeuicc/UnprivilegedOpenEuiccApplication.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index 9a2a855..ba15e37 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import im.angry.openeuicc.common.R +import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -44,16 +45,7 @@ class LogsActivity : AppCompatActivity() { private suspend fun reload() = withContext(Dispatchers.Main) { swipeRefresh.isRefreshing = true - val logStr = withContext(Dispatchers.IO) { - try { - Runtime.getRuntime().exec("logcat -t 1024").inputStream.readBytes() - .decodeToString() - } catch (_: Exception) { - "" - } - } - - logText.text = logStr + logText.text = intent.extras?.getString("log") ?: readSelfLog() swipeRefresh.isRefreshing = false diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index b2690b0..dc90ab1 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -8,9 +8,11 @@ import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import net.typeblog.lpac_jni.LocalProfileInfo import java.lang.RuntimeException import kotlin.coroutines.resume @@ -26,6 +28,15 @@ val Context.selfAppVersion: String throw RuntimeException(e) } +suspend fun readSelfLog(lines: Int = 2048): String = withContext(Dispatchers.IO) { + try { + Runtime.getRuntime().exec("logcat -t $lines").inputStream.readBytes() + .decodeToString() + } catch (_: Exception) { + "" + } +} + interface OpenEuiccContextMarker { val openEuiccMarkerContext: Context get() = when (this) { diff --git a/app-unpriv/src/main/AndroidManifest.xml b/app-unpriv/src/main/AndroidManifest.xml index bce6831..e72b112 100644 --- a/app-unpriv/src/main/AndroidManifest.xml +++ b/app-unpriv/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + Intent(this, LogsActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra("log", runBlocking { readSelfLog() }) + startActivity(this) + exitProcess(-1) + } + } + } +} \ No newline at end of file From e48a919335583f1f86d6955a1fc41ce42bb219ee Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 3 Mar 2024 13:22:46 -0500 Subject: [PATCH 024/472] feat: Allow exporting logs as txt --- .../im/angry/openeuicc/ui/LogsActivity.kt | 31 +++++++++++++++++++ .../main/res/drawable/ic_save_as_black.xml | 5 +++ .../src/main/res/menu/activity_logs.xml | 9 ++++++ app-common/src/main/res/values/strings.xml | 3 ++ 4 files changed, 48 insertions(+) create mode 100644 app-common/src/main/res/drawable/ic_save_as_black.xml create mode 100644 app-common/src/main/res/menu/activity_logs.xml diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index ba15e37..1a7e50f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -1,9 +1,13 @@ package im.angry.openeuicc.ui +import android.icu.text.SimpleDateFormat import android.os.Bundle +import android.view.Menu +import android.view.MenuItem import android.view.View import android.widget.ScrollView import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -12,12 +16,24 @@ import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.FileOutputStream +import java.util.Date class LogsActivity : AppCompatActivity() { private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var scrollView: ScrollView private lateinit var logText: TextView + private val saveLogs = + registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> + if (uri == null) return@registerForActivityResult + contentResolver.openFileDescriptor(uri, "w")?.use { + FileOutputStream(it.fileDescriptor).use { os -> + os.write(logText.text.toString().encodeToByteArray()) + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_logs) @@ -42,6 +58,21 @@ class LogsActivity : AppCompatActivity() { } } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.activity_logs, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + R.id.save -> { + saveLogs.launch(getString(R.string.logs_filename_template, + SimpleDateFormat.getDateTimeInstance().format(Date()) + )) + true + } + else -> super.onOptionsItemSelected(item) + } + private suspend fun reload() = withContext(Dispatchers.Main) { swipeRefresh.isRefreshing = true diff --git a/app-common/src/main/res/drawable/ic_save_as_black.xml b/app-common/src/main/res/drawable/ic_save_as_black.xml new file mode 100644 index 0000000..aaee678 --- /dev/null +++ b/app-common/src/main/res/drawable/ic_save_as_black.xml @@ -0,0 +1,5 @@ + + + diff --git a/app-common/src/main/res/menu/activity_logs.xml b/app-common/src/main/res/menu/activity_logs.xml new file mode 100644 index 0000000..4fa3aeb --- /dev/null +++ b/app-common/src/main/res/menu/activity_logs.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 9d8932a..040c910 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -48,6 +48,9 @@ Process Delete + Save + Logs at %s + Settings Notifications eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here. From 770083523db3cec28184edf8a169a5eafd09e1d8 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 3 Mar 2024 20:29:18 -0500 Subject: [PATCH 025/472] refactor: Extract an interface from EuiccChannelManager Eventually, we would like EuiccChannelManager to become a Service instead of just any random class. --- .../angry/openeuicc/OpenEuiccApplication.kt | 3 +- .../openeuicc/core/EuiccChannelManager.kt | 84 ++++++++++--------- .../openeuicc/core/IEuiccChannelManager.kt | 47 +++++++++++ .../im/angry/openeuicc/ui/MainActivity.kt | 13 +-- .../java/im/angry/openeuicc/util/Utils.kt | 4 +- .../PrivilegedOpenEuiccApplication.kt | 4 +- .../util/PrivilegedTelephonyUtils.kt | 6 +- 7 files changed, 105 insertions(+), 56 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt b/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt index e917aec..0b2ccec 100644 --- a/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt +++ b/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt @@ -5,6 +5,7 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import com.google.android.material.color.DynamicColors import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager import im.angry.openeuicc.util.PreferenceRepository open class OpenEuiccApplication : Application() { @@ -19,7 +20,7 @@ open class OpenEuiccApplication : Application() { getSystemService(TelephonyManager::class.java)!! } - open val euiccChannelManager: EuiccChannelManager by lazy { + open val euiccChannelManager: IEuiccChannelManager by lazy { EuiccChannelManager(this) } diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index 23c21b9..e031794 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import java.lang.IllegalArgumentException -open class EuiccChannelManager(protected val context: Context) { +open class EuiccChannelManager(protected val context: Context) : IEuiccChannelManager { companion object { const val TAG = "EuiccChannelManager" } @@ -32,9 +32,9 @@ open class EuiccChannelManager(protected val context: Context) { get() = (0..? = + runBlocking { for (card in uiccCards) { if (card.physicalSlotIndex != physicalSlotId) continue - for (port in card.ports) { - tryOpenEuiccChannel(port)?.let { return@withContext it } + return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) } + .ifEmpty { null } + } + return@runBlocking null + } + + override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = + runBlocking { + withContext(Dispatchers.IO) { + uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card -> + card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) } } } - - null } - } - fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List? = runBlocking { - for (card in uiccCards) { - if (card.physicalSlotIndex != physicalSlotId) continue - return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) } - .ifEmpty { null } - } - return@runBlocking null - } - - fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking { - withContext(Dispatchers.IO) { - uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card -> - card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) } - } - } - } - - suspend fun enumerateEuiccChannels() { + override suspend fun enumerateEuiccChannels() { withContext(Dispatchers.IO) { ensureSEService() for (uiccInfo in uiccCards) { for (port in uiccInfo.ports) { if (tryOpenEuiccChannel(port) != null) { - Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}") + Log.d( + TAG, + "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}" + ) } } } } } - val knownChannels: List + override val knownChannels: List get() = channels.toList() - fun invalidate() { + override fun invalidate() { for (channel in channels) { channel.close() } @@ -161,8 +171,4 @@ open class EuiccChannelManager(protected val context: Context) { seService?.shutdown() seService = null } - - open fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - // No-op for unprivileged - } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt new file mode 100644 index 0000000..8a6ab54 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt @@ -0,0 +1,47 @@ +package im.angry.openeuicc.core + +interface IEuiccChannelManager { + val knownChannels: List + + /** + * Scan all possible sources for EuiccChannels and have them cached for future use + */ + suspend fun enumerateEuiccChannels() + + /** + * Returns the EuiccChannel corresponding to a **logical** slot + */ + fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? + + /** + * Returns the first EuiccChannel corresponding to a **physical** slot + * If the physical slot supports MEP and has multiple ports, it is undefined + * which of the two channels will be returned. + */ + fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? + + /** + * Returns all EuiccChannels corresponding to a **physical** slot + * Multiple channels are possible in the case of MEP + */ + fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List? + + /** + * Returns the EuiccChannel corresponding to a **physical** slot and a port ID + */ + fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? + + /** + * Invalidate all EuiccChannels previously known by this Manager + */ + fun invalidate() + + /** + * If possible, trigger the system to update the cached list of profiles + * This is only expected to be implemented when the application is privileged + * TODO: Remove this from the common interface + */ + fun notifyEuiccProfilesChanged(logicalSlotId: Int) { + // no-op by default + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index 452ed76..af6cbf4 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -14,7 +14,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import im.angry.openeuicc.common.R import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -25,8 +24,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { const val TAG = "MainActivity" } - protected lateinit var manager: EuiccChannelManager - private lateinit var spinnerAdapter: ArrayAdapter private lateinit var spinner: Spinner @@ -45,8 +42,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { tm = telephonyManager - manager = euiccChannelManager - spinnerAdapter = ArrayAdapter(this, R.layout.spinner_item) lifecycleScope.launch { @@ -99,19 +94,19 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { private suspend fun init() { withContext(Dispatchers.IO) { - manager.enumerateEuiccChannels() - manager.knownChannels.forEach { + euiccChannelManager.enumerateEuiccChannels() + euiccChannelManager.knownChannels.forEach { Log.d(TAG, "slot ${it.slotId} port ${it.portId}") Log.d(TAG, it.lpa.eID) // Request the system to refresh the list of profiles every time we start // Note that this is currently supposed to be no-op when unprivileged, // but it could change in the future - manager.notifyEuiccProfilesChanged(it.logicalSlotId) + euiccChannelManager.notifyEuiccProfilesChanged(it.logicalSlotId) } } withContext(Dispatchers.Main) { - manager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> + euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) fragments.add(createEuiccManagementFragment(channel)) } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index dc90ab1..3c21898 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -7,7 +7,7 @@ import android.telephony.TelephonyManager import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -48,7 +48,7 @@ interface OpenEuiccContextMarker { val openEuiccApplication: OpenEuiccApplication get() = openEuiccMarkerContext.applicationContext as OpenEuiccApplication - val euiccChannelManager: EuiccChannelManager + val euiccChannelManager: IEuiccChannelManager get() = openEuiccApplication.euiccChannelManager val telephonyManager: TelephonyManager diff --git a/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt b/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt index 8b3ef99..28ea49d 100644 --- a/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt +++ b/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt @@ -1,10 +1,10 @@ package im.angry.openeuicc -import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager class PrivilegedOpenEuiccApplication: OpenEuiccApplication() { - override val euiccChannelManager: EuiccChannelManager by lazy { + override val euiccChannelManager: IEuiccChannelManager by lazy { PrivilegedEuiccChannelManager(this) } diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt index 4675ab9..4cb4932 100644 --- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt @@ -4,7 +4,7 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.UiccSlotMapping import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager import kotlinx.coroutines.runBlocking import java.lang.Exception @@ -14,7 +14,7 @@ val TelephonyManager.supportsDSDS: Boolean val TelephonyManager.dsdsEnabled: Boolean get() = activeModemCount >= 2 -fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) { +fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: Boolean) { runBlocking { euiccManager.enumerateEuiccChannels() } @@ -32,7 +32,7 @@ fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: // Disable eSIM profiles before switching the slot mapping // This ensures that unmapped eSIM ports never have "ghost" profiles enabled fun TelephonyManager.updateSimSlotMapping( - euiccManager: EuiccChannelManager, newMapping: Collection, + euiccManager: IEuiccChannelManager, newMapping: Collection, currentMapping: Collection = simSlotMapping ) { val unmapped = currentMapping.filterNot { mapping -> From 2d1c96023a8669036dc2652e13414bcb4f05a893 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 17:17:20 -0500 Subject: [PATCH 026/472] refactor: Condense dependency management to a rudimentary dependency injection subpackage --- .../angry/openeuicc/OpenEuiccApplication.kt | 27 +++++-------------- .../openeuicc/core/EuiccChannelManager.kt | 2 +- .../im/angry/openeuicc/di/AppContainer.kt | 13 +++++++++ .../angry/openeuicc/di/DefaultAppContainer.kt | 26 ++++++++++++++++++ .../openeuicc/ui/ProfileDownloadFragment.kt | 2 +- .../java/im/angry/openeuicc/util/Utils.kt | 8 ++++-- .../PrivilegedOpenEuiccApplication.kt | 9 ++++--- .../core/PrivilegedEuiccChannelManager.kt | 2 +- .../openeuicc/di/PrivilegedAppContainer.kt | 11 ++++++++ .../openeuicc/service/OpenEuiccService.kt | 2 +- .../openeuicc/ui/PrivilegedMainActivity.kt | 2 +- .../angry/openeuicc/ui/SlotMappingFragment.kt | 16 +++++------ 12 files changed, 78 insertions(+), 42 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt create mode 100644 app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt create mode 100644 app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt b/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt index 0b2ccec..6cd22ce 100644 --- a/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt +++ b/app-common/src/main/java/im/angry/openeuicc/OpenEuiccApplication.kt @@ -1,34 +1,19 @@ package im.angry.openeuicc import android.app.Application -import android.telephony.SubscriptionManager -import android.telephony.TelephonyManager import com.google.android.material.color.DynamicColors -import im.angry.openeuicc.core.EuiccChannelManager -import im.angry.openeuicc.core.IEuiccChannelManager -import im.angry.openeuicc.util.PreferenceRepository +import im.angry.openeuicc.di.AppContainer +import im.angry.openeuicc.di.DefaultAppContainer open class OpenEuiccApplication : Application() { + open val appContainer: AppContainer by lazy { + DefaultAppContainer(this) + } + override fun onCreate() { super.onCreate() // Observe dynamic colors changes DynamicColors.applyToActivitiesIfAvailable(this) } - - val telephonyManager by lazy { - getSystemService(TelephonyManager::class.java)!! - } - - open val euiccChannelManager: IEuiccChannelManager by lazy { - EuiccChannelManager(this) - } - - val subscriptionManager by lazy { - getSystemService(SubscriptionManager::class.java)!! - } - - val preferenceRepository by lazy { - PreferenceRepository(this) - } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index e031794..9033d34 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -25,7 +25,7 @@ open class EuiccChannelManager(protected val context: Context) : IEuiccChannelMa private val lock = Mutex() protected val tm by lazy { - (context.applicationContext as OpenEuiccApplication).telephonyManager + (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager } protected open val uiccCards: Collection diff --git a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt new file mode 100644 index 0000000..65c2aa9 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt @@ -0,0 +1,13 @@ +package im.angry.openeuicc.di + +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.util.* + +interface AppContainer { + val telephonyManager: TelephonyManager + val euiccChannelManager: IEuiccChannelManager + val subscriptionManager: SubscriptionManager + val preferenceRepository: PreferenceRepository +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt new file mode 100644 index 0000000..dd7826c --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt @@ -0,0 +1,26 @@ +package im.angry.openeuicc.di + +import android.content.Context +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.util.* + +open class DefaultAppContainer(context: Context) : AppContainer { + override val telephonyManager by lazy { + context.getSystemService(TelephonyManager::class.java)!! + } + + override val euiccChannelManager: IEuiccChannelManager by lazy { + EuiccChannelManager(context) + } + + override val subscriptionManager by lazy { + context.getSystemService(SubscriptionManager::class.java)!! + } + + override val preferenceRepository by lazy { + PreferenceRepository(context) + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt index 360da4f..43fc1ee 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -121,7 +121,7 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(), super.onStart() profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable( try { - openEuiccApplication.telephonyManager.getImei(channel.logicalSlotId) + telephonyManager.getImei(channel.logicalSlotId) } catch (e: Exception) { "" } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 3c21898..57626e2 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.di.AppContainer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -48,11 +49,14 @@ interface OpenEuiccContextMarker { val openEuiccApplication: OpenEuiccApplication get() = openEuiccMarkerContext.applicationContext as OpenEuiccApplication + val appContainer: AppContainer + get() = openEuiccApplication.appContainer + val euiccChannelManager: IEuiccChannelManager - get() = openEuiccApplication.euiccChannelManager + get() = appContainer.euiccChannelManager val telephonyManager: TelephonyManager - get() = openEuiccApplication.telephonyManager + get() = appContainer.telephonyManager } val LocalProfileInfo.isEnabled: Boolean diff --git a/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt b/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt index 28ea49d..2d8e5ee 100644 --- a/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt +++ b/app/src/main/java/im/angry/openeuicc/PrivilegedOpenEuiccApplication.kt @@ -1,16 +1,17 @@ package im.angry.openeuicc -import im.angry.openeuicc.core.IEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager +import im.angry.openeuicc.di.AppContainer +import im.angry.openeuicc.di.PrivilegedAppContainer class PrivilegedOpenEuiccApplication: OpenEuiccApplication() { - override val euiccChannelManager: IEuiccChannelManager by lazy { - PrivilegedEuiccChannelManager(this) + override val appContainer: AppContainer by lazy { + PrivilegedAppContainer(this) } override fun onCreate() { super.onCreate() - (euiccChannelManager as PrivilegedEuiccChannelManager).closeAllStaleChannels() + (appContainer.euiccChannelManager as PrivilegedEuiccChannelManager).closeAllStaleChannels() } } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt index 288cebd..75b2b22 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt @@ -48,7 +48,7 @@ class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(conte } override fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - (context.applicationContext as OpenEuiccApplication).subscriptionManager.apply { + (context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply { findEuiccChannelBySlotBlocking(logicalSlotId)?.let { tryRefreshCachedEuiccInfo(it.cardId) } diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt new file mode 100644 index 0000000..a849a27 --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -0,0 +1,11 @@ +package im.angry.openeuicc.di + +import android.content.Context +import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.PrivilegedEuiccChannelManager + +class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { + override val euiccChannelManager: IEuiccChannelManager by lazy { + PrivilegedEuiccChannelManager(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt index 6a08da5..23f1444 100644 --- a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt +++ b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt @@ -238,7 +238,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { } val success = channel.lpa .setNickname(iccid, nickname!!) - openEuiccApplication.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId) + appContainer.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId) return if (success) { RESULT_OK } else { diff --git a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt index 64e8ca9..1261932 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt @@ -26,7 +26,7 @@ class PrivilegedMainActivity : MainActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { R.id.dsds -> { - tm.setDsdsEnabled(openEuiccApplication.euiccChannelManager, !item.isChecked) + tm.setDsdsEnabled(euiccChannelManager, !item.isChecked) Toast.makeText(this, R.string.toast_dsds_switched, Toast.LENGTH_LONG).show() finish() true diff --git a/app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt index ba60fd7..4c77bac 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt @@ -32,12 +32,8 @@ class SlotMappingFragment: BaseMaterialDialogFragment(), const val TAG = "SlotMappingFragment" } - private val tm: TelephonyManager by lazy { - (requireContext().applicationContext as OpenEuiccApplication).telephonyManager - } - private val ports: List by lazy { - tm.uiccCardsInfoCompat.flatMap { it.ports } + telephonyManager.uiccCardsInfoCompat.flatMap { it.ports } } private val portsDesc: List by lazy { @@ -81,7 +77,7 @@ class SlotMappingFragment: BaseMaterialDialogFragment(), private fun init() { lifecycleScope.launch(Dispatchers.Main) { val mapping = withContext(Dispatchers.IO) { - tm.simSlotMapping + telephonyManager.simSlotMapping } adapter = SlotMappingAdapter(mapping.toMutableList().apply { @@ -100,14 +96,14 @@ class SlotMappingFragment: BaseMaterialDialogFragment(), withContext(Dispatchers.IO) { // Use the utility method from PrivilegedTelephonyUtils to ensure // unmapped ports have all profiles disabled - tm.updateSimSlotMapping(openEuiccApplication.euiccChannelManager, adapter.mappings) + telephonyManager.updateSimSlotMapping(euiccChannelManager, adapter.mappings) } } catch (e: Exception) { Toast.makeText(requireContext(), R.string.slot_mapping_failure, Toast.LENGTH_LONG).show() return@launch } Toast.makeText(requireContext(), R.string.slot_mapping_completed, Toast.LENGTH_LONG).show() - openEuiccApplication.euiccChannelManager.invalidate() + euiccChannelManager.invalidate() requireActivity().finish() } } @@ -115,7 +111,7 @@ class SlotMappingFragment: BaseMaterialDialogFragment(), private suspend fun buildHelpText() = withContext(Dispatchers.IO) { val nLogicalSlots = adapter.mappings.size - val cards = openEuiccApplication.telephonyManager.uiccCardsInfoCompat + val cards = telephonyManager.uiccCardsInfoCompat val nPhysicalSlots = cards.size var idxMepCard = -1 @@ -129,7 +125,7 @@ class SlotMappingFragment: BaseMaterialDialogFragment(), } val mayEnableDSDS = - openEuiccApplication.telephonyManager.supportsDSDS && !openEuiccApplication.telephonyManager.dsdsEnabled + telephonyManager.supportsDSDS && !telephonyManager.dsdsEnabled val extraText = if (nLogicalSlots == 1 && mayEnableDSDS) { getString(R.string.slot_mapping_help_dsds) From 49af0ffee9719497fa439fcde6fa81738a1bbebc Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 18:23:53 -0500 Subject: [PATCH 027/472] CompatibilityCheck: Return FAILURE_UNKNOWN when no SIM readers are found --- .../java/im/angry/openeuicc/util/CompatibilityCheck.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index 3d9c47b..843c487 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -132,7 +132,13 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili override suspend fun doCheck(): State { val seService = connectSEService(context) - val (validSlotIds, result) = seService.readers.filter { it.isSIM }.map { + val readers = seService.readers.filter { it.isSIM } + if (readers.isEmpty()) { + failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown) + return State.FAILURE_UNKNOWN + } + + val (validSlotIds, result) = readers.map { try { it.openSession().openLogicalChannel(ISDR_AID)?.close() Pair(it.slotIndex, State.SUCCESS) From a101ae6805340053c9d783b2a886965070bf34fb Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 18:38:01 -0500 Subject: [PATCH 028/472] CompatibilityCheck: Improve OMAPI connectivity check Stop failing the test if only some slots can be seen. Display a text warning users of that, but don't appear as a failure. --- .../main/java/im/angry/openeuicc/util/CompatibilityCheck.kt | 6 ++++-- app-unpriv/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index 843c487..93d49be 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -47,11 +47,13 @@ abstract class CompatibilityCheck(context: Context) { abstract val title: String protected abstract val defaultDescription: String + protected lateinit var successDescription: String protected lateinit var failureDescription: String val description: String get() = when { (state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription + state == State.SUCCESS && this::successDescription.isInitialized -> successDescription else -> defaultDescription } @@ -111,9 +113,9 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck( failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail) return State.FAILURE } else if (simReaders.size < tm.activeModemCountCompat) { - failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail_sim_number, + successDescription = context.getString(R.string.compatibility_check_omapi_connectivity_partial_success_sim_number, simReaders.map { it.slotIndex }.joinToString(", ")) - return State.FAILURE + return State.SUCCESS } return State.SUCCESS diff --git a/app-unpriv/src/main/res/values/strings.xml b/app-unpriv/src/main/res/values/strings.xml index 3a20af1..8845a58 100644 --- a/app-unpriv/src/main/res/values/strings.xml +++ b/app-unpriv/src/main/res/values/strings.xml @@ -9,8 +9,8 @@ Your device has no support for accessing SIM cards via OMAPI. OMAPI Connectivity Does your device allow access to Secure Elements on SIM cards via OMAPI? - Unable to detect Secure Element readers for SIM cards via OMAPI. - Only the following SIM slots are accessible via OMAPI: %s. + Unable to detect Secure Element readers for SIM cards via OMAPI. If you have not inserted a SIM in this device, try inserting one and retry this check. + Successfully detected Secure Element access, but only for the following SIM slots: %s. ISD-R Channel Access Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI? Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already. From aef399dad013b53aaaa943cf01a9ab7038e11568 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 18:39:40 -0500 Subject: [PATCH 029/472] CompatibilityCheck: Explain that the user might want to contact the ROM developer --- app-unpriv/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-unpriv/src/main/res/values/strings.xml b/app-unpriv/src/main/res/values/strings.xml index 8845a58..588c073 100644 --- a/app-unpriv/src/main/res/values/strings.xml +++ b/app-unpriv/src/main/res/values/strings.xml @@ -6,7 +6,7 @@ System Features Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support. Your device has no telephony features. - Your device has no support for accessing SIM cards via OMAPI. + Your device has no support for accessing SIM cards via OMAPI. If you are using a custom ROM, consider contacting the developer to determine whether it is due to hardware or a missing feature declaration in the OS. OMAPI Connectivity Does your device allow access to Secure Elements on SIM cards via OMAPI? Unable to detect Secure Element readers for SIM cards via OMAPI. If you have not inserted a SIM in this device, try inserting one and retry this check. From 6356601467f768a5957ecc0c9480b875efb753d4 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 18:43:37 -0500 Subject: [PATCH 030/472] CompatibilityCheck: Make connectivity fail a "Unknown" failure --- .../src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index 93d49be..ae5a4da 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -111,7 +111,7 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck( val simReaders = seService.readers.filter { it.isSIM } if (simReaders.isEmpty()) { failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail) - return State.FAILURE + return State.FAILURE_UNKNOWN } else if (simReaders.size < tm.activeModemCountCompat) { successDescription = context.getString(R.string.compatibility_check_omapi_connectivity_partial_success_sim_number, simReaders.map { it.slotIndex }.joinToString(", ")) From 4dd14d23f24d6dd7694571970c56cb98499bd85a Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 18:51:10 -0500 Subject: [PATCH 031/472] refactor: Add UiComponentFactory to manual DI --- .../main/java/im/angry/openeuicc/di/AppContainer.kt | 1 + .../java/im/angry/openeuicc/di/DefaultAppContainer.kt | 4 ++++ .../im/angry/openeuicc/di/DefaultUiComponentFactory.kt | 9 +++++++++ .../java/im/angry/openeuicc/di/UiComponentFactory.kt | 8 ++++++++ .../main/java/im/angry/openeuicc/ui/MainActivity.kt | 6 +----- .../im/angry/openeuicc/di/PrivilegedAppContainer.kt | 4 ++++ .../angry/openeuicc/di/PrivilegedUiComponentFactory.kt | 10 ++++++++++ .../im/angry/openeuicc/ui/PrivilegedMainActivity.kt | 4 ---- 8 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt create mode 100644 app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt create mode 100644 app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt index 65c2aa9..a29fcf8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt @@ -10,4 +10,5 @@ interface AppContainer { val euiccChannelManager: IEuiccChannelManager val subscriptionManager: SubscriptionManager val preferenceRepository: PreferenceRepository + val uiComponentFactory: UiComponentFactory } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt index dd7826c..6a03835 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt @@ -23,4 +23,8 @@ open class DefaultAppContainer(context: Context) : AppContainer { override val preferenceRepository by lazy { PreferenceRepository(context) } + + override val uiComponentFactory by lazy { + DefaultUiComponentFactory() + } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt new file mode 100644 index 0000000..86af007 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt @@ -0,0 +1,9 @@ +package im.angry.openeuicc.di + +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.ui.EuiccManagementFragment + +open class DefaultUiComponentFactory : UiComponentFactory { + override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = + EuiccManagementFragment.newInstance(channel.slotId, channel.portId) +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt b/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt new file mode 100644 index 0000000..d311876 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt @@ -0,0 +1,8 @@ +package im.angry.openeuicc.di + +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.ui.EuiccManagementFragment + +interface UiComponentFactory { + fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index af6cbf4..ca6715d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -88,10 +88,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { else -> super.onOptionsItemSelected(item) } - - protected open fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = - EuiccManagementFragment.newInstance(channel.slotId, channel.portId) - private suspend fun init() { withContext(Dispatchers.IO) { euiccChannelManager.enumerateEuiccChannels() @@ -108,7 +104,7 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { withContext(Dispatchers.Main) { euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) - fragments.add(createEuiccManagementFragment(channel)) + fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) } if (fragments.isNotEmpty()) { diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt index a849a27..64e45eb 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -8,4 +8,8 @@ class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { override val euiccChannelManager: IEuiccChannelManager by lazy { PrivilegedEuiccChannelManager(context) } + + override val uiComponentFactory by lazy { + PrivilegedUiComponentFactory() + } } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt new file mode 100644 index 0000000..d3c5cdb --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt @@ -0,0 +1,10 @@ +package im.angry.openeuicc.di + +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.ui.EuiccManagementFragment +import im.angry.openeuicc.ui.PrivilegedEuiccManagementFragment + +class PrivilegedUiComponentFactory : DefaultUiComponentFactory() { + override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = + PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId) +} \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt index 1261932..f3c2b3c 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt @@ -4,7 +4,6 @@ import android.view.Menu import android.view.MenuItem import android.widget.Toast import im.angry.openeuicc.R -import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.util.* class PrivilegedMainActivity : MainActivity() { @@ -37,7 +36,4 @@ class PrivilegedMainActivity : MainActivity() { } else -> super.onOptionsItemSelected(item) } - - override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = - PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId) } \ No newline at end of file From 7c6b4ebee5cb9dc4690b2336bb8931f63e7820ca Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 19:06:05 -0500 Subject: [PATCH 032/472] refactor: IEuiccChannelManager -> EuiccChannelManager --- .../core/DefaultEuiccChannelManager.kt | 174 +++++++++++++++ .../openeuicc/core/EuiccChannelManager.kt | 199 ++++-------------- .../openeuicc/core/IEuiccChannelManager.kt | 47 ----- .../im/angry/openeuicc/di/AppContainer.kt | 4 +- .../angry/openeuicc/di/DefaultAppContainer.kt | 6 +- .../java/im/angry/openeuicc/util/Utils.kt | 4 +- .../core/PrivilegedEuiccChannelManager.kt | 2 +- .../openeuicc/di/PrivilegedAppContainer.kt | 4 +- .../util/PrivilegedTelephonyUtils.kt | 6 +- 9 files changed, 223 insertions(+), 223 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt delete mode 100644 app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt new file mode 100644 index 0000000..1883a11 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt @@ -0,0 +1,174 @@ +package im.angry.openeuicc.core + +import android.content.Context +import android.se.omapi.SEService +import android.telephony.SubscriptionManager +import android.util.Log +import im.angry.openeuicc.OpenEuiccApplication +import im.angry.openeuicc.util.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import java.lang.IllegalArgumentException + +open class DefaultEuiccChannelManager(protected val context: Context) : EuiccChannelManager { + companion object { + const val TAG = "EuiccChannelManager" + } + + private val channels = mutableListOf() + + private var seService: SEService? = null + + private val lock = Mutex() + + protected val tm by lazy { + (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager + } + + protected open val uiccCards: Collection + get() = (0..? = + runBlocking { + for (card in uiccCards) { + if (card.physicalSlotIndex != physicalSlotId) continue + return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) } + .ifEmpty { null } + } + return@runBlocking null + } + + override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = + runBlocking { + withContext(Dispatchers.IO) { + uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card -> + card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) } + } + } + } + + override suspend fun enumerateEuiccChannels() { + withContext(Dispatchers.IO) { + ensureSEService() + + for (uiccInfo in uiccCards) { + for (port in uiccInfo.ports) { + if (tryOpenEuiccChannel(port) != null) { + Log.d( + TAG, + "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}" + ) + } + } + } + } + } + + override val knownChannels: List + get() = channels.toList() + + override fun invalidate() { + for (channel in channels) { + channel.close() + } + + channels.clear() + seService?.shutdown() + seService = null + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index 9033d34..b0bce1d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -1,174 +1,47 @@ package im.angry.openeuicc.core -import android.content.Context -import android.se.omapi.SEService -import android.telephony.SubscriptionManager -import android.util.Log -import im.angry.openeuicc.OpenEuiccApplication -import im.angry.openeuicc.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import java.lang.IllegalArgumentException +interface EuiccChannelManager { + val knownChannels: List -open class EuiccChannelManager(protected val context: Context) : IEuiccChannelManager { - companion object { - const val TAG = "EuiccChannelManager" - } + /** + * Scan all possible sources for EuiccChannels and have them cached for future use + */ + suspend fun enumerateEuiccChannels() - private val channels = mutableListOf() + /** + * Returns the EuiccChannel corresponding to a **logical** slot + */ + fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? - private var seService: SEService? = null + /** + * Returns the first EuiccChannel corresponding to a **physical** slot + * If the physical slot supports MEP and has multiple ports, it is undefined + * which of the two channels will be returned. + */ + fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? - private val lock = Mutex() + /** + * Returns all EuiccChannels corresponding to a **physical** slot + * Multiple channels are possible in the case of MEP + */ + fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List? - protected val tm by lazy { - (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager - } + /** + * Returns the EuiccChannel corresponding to a **physical** slot and a port ID + */ + fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? - protected open val uiccCards: Collection - get() = (0..? = - runBlocking { - for (card in uiccCards) { - if (card.physicalSlotIndex != physicalSlotId) continue - return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) } - .ifEmpty { null } - } - return@runBlocking null - } - - override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = - runBlocking { - withContext(Dispatchers.IO) { - uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card -> - card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) } - } - } - } - - override suspend fun enumerateEuiccChannels() { - withContext(Dispatchers.IO) { - ensureSEService() - - for (uiccInfo in uiccCards) { - for (port in uiccInfo.ports) { - if (tryOpenEuiccChannel(port) != null) { - Log.d( - TAG, - "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}" - ) - } - } - } - } - } - - override val knownChannels: List - get() = channels.toList() - - override fun invalidate() { - for (channel in channels) { - channel.close() - } - - channels.clear() - seService?.shutdown() - seService = null + /** + * If possible, trigger the system to update the cached list of profiles + * This is only expected to be implemented when the application is privileged + * TODO: Remove this from the common interface + */ + fun notifyEuiccProfilesChanged(logicalSlotId: Int) { + // no-op by default } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt deleted file mode 100644 index 8a6ab54..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt +++ /dev/null @@ -1,47 +0,0 @@ -package im.angry.openeuicc.core - -interface IEuiccChannelManager { - val knownChannels: List - - /** - * Scan all possible sources for EuiccChannels and have them cached for future use - */ - suspend fun enumerateEuiccChannels() - - /** - * Returns the EuiccChannel corresponding to a **logical** slot - */ - fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? - - /** - * Returns the first EuiccChannel corresponding to a **physical** slot - * If the physical slot supports MEP and has multiple ports, it is undefined - * which of the two channels will be returned. - */ - fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? - - /** - * Returns all EuiccChannels corresponding to a **physical** slot - * Multiple channels are possible in the case of MEP - */ - fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List? - - /** - * Returns the EuiccChannel corresponding to a **physical** slot and a port ID - */ - fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? - - /** - * Invalidate all EuiccChannels previously known by this Manager - */ - fun invalidate() - - /** - * If possible, trigger the system to update the cached list of profiles - * This is only expected to be implemented when the application is privileged - * TODO: Remove this from the common interface - */ - fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - // no-op by default - } -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt index a29fcf8..a62eff4 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt @@ -2,12 +2,12 @@ package im.angry.openeuicc.di import android.telephony.SubscriptionManager import android.telephony.TelephonyManager -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* interface AppContainer { val telephonyManager: TelephonyManager - val euiccChannelManager: IEuiccChannelManager + val euiccChannelManager: EuiccChannelManager val subscriptionManager: SubscriptionManager val preferenceRepository: PreferenceRepository val uiComponentFactory: UiComponentFactory diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt index 6a03835..9f051e8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt @@ -3,8 +3,8 @@ package im.angry.openeuicc.di import android.content.Context import android.telephony.SubscriptionManager import android.telephony.TelephonyManager +import im.angry.openeuicc.core.DefaultEuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager -import im.angry.openeuicc.core.IEuiccChannelManager import im.angry.openeuicc.util.* open class DefaultAppContainer(context: Context) : AppContainer { @@ -12,8 +12,8 @@ open class DefaultAppContainer(context: Context) : AppContainer { context.getSystemService(TelephonyManager::class.java)!! } - override val euiccChannelManager: IEuiccChannelManager by lazy { - EuiccChannelManager(context) + override val euiccChannelManager: EuiccChannelManager by lazy { + DefaultEuiccChannelManager(context) } override val subscriptionManager by lazy { diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 57626e2..33699d7 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -7,7 +7,7 @@ import android.telephony.TelephonyManager import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.di.AppContainer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,7 +52,7 @@ interface OpenEuiccContextMarker { val appContainer: AppContainer get() = openEuiccApplication.appContainer - val euiccChannelManager: IEuiccChannelManager + val euiccChannelManager: EuiccChannelManager get() = appContainer.euiccChannelManager val telephonyManager: TelephonyManager diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt index 75b2b22..0ebaa81 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt @@ -7,7 +7,7 @@ import im.angry.openeuicc.util.* import java.lang.Exception import java.lang.IllegalArgumentException -class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) { +class PrivilegedEuiccChannelManager(context: Context): DefaultEuiccChannelManager(context) { override val uiccCards: Collection get() = tm.uiccCardsInfoCompat diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt index 64e45eb..dc3921e 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -1,11 +1,11 @@ package im.angry.openeuicc.di import android.content.Context -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { - override val euiccChannelManager: IEuiccChannelManager by lazy { + override val euiccChannelManager: EuiccChannelManager by lazy { PrivilegedEuiccChannelManager(context) } diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt index 4cb4932..4675ab9 100644 --- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt @@ -4,7 +4,7 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.UiccSlotMapping import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import kotlinx.coroutines.runBlocking import java.lang.Exception @@ -14,7 +14,7 @@ val TelephonyManager.supportsDSDS: Boolean val TelephonyManager.dsdsEnabled: Boolean get() = activeModemCount >= 2 -fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: Boolean) { +fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) { runBlocking { euiccManager.enumerateEuiccChannels() } @@ -32,7 +32,7 @@ fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: // Disable eSIM profiles before switching the slot mapping // This ensures that unmapped eSIM ports never have "ghost" profiles enabled fun TelephonyManager.updateSimSlotMapping( - euiccManager: IEuiccChannelManager, newMapping: Collection, + euiccManager: EuiccChannelManager, newMapping: Collection, currentMapping: Collection = simSlotMapping ) { val unmapped = currentMapping.filterNot { mapping -> From 1a69c5294b56912ce71516f3b6c8be4c490d747b Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 19:30:04 -0500 Subject: [PATCH 033/472] refactor: Use DI techniques for EuiccChannel's as well --- .../core/DefaultEuiccChannelFactory.kt | 43 ++++++++++++ .../core/DefaultEuiccChannelManager.kt | 65 ++++--------------- .../openeuicc/core/EuiccChannelFactory.kt | 16 +++++ .../im/angry/openeuicc/di/AppContainer.kt | 2 + .../angry/openeuicc/di/DefaultAppContainer.kt | 7 +- .../core/PrivilegedEuiccChannelFactory.kt | 43 ++++++++++++ .../core/PrivilegedEuiccChannelManager.kt | 30 ++------- .../openeuicc/di/PrivilegedAppContainer.kt | 7 +- 8 files changed, 133 insertions(+), 80 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt create mode 100644 app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt create mode 100644 app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt new file mode 100644 index 0000000..15e0191 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -0,0 +1,43 @@ +package im.angry.openeuicc.core + +import android.content.Context +import android.se.omapi.SEService +import android.util.Log +import im.angry.openeuicc.util.* +import java.lang.IllegalArgumentException + +open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory { + private var seService: SEService? = null + + private suspend fun ensureSEService() { + if (seService == null) { + seService = connectSEService(context) + } + } + + override suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? { + if (port.portIndex != 0) { + Log.w(DefaultEuiccChannelManager.TAG, "OMAPI channel attempted on non-zero portId, this may or may not work.") + } + + ensureSEService() + + Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}") + try { + return OmapiChannel(seService!!, port) + } catch (e: IllegalArgumentException) { + // Failed + Log.w( + DefaultEuiccChannelManager.TAG, + "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}." + ) + } + + return null + } + + override fun cleanup() { + seService?.shutdown() + seService = null + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt index 1883a11..a978677 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt @@ -1,69 +1,41 @@ package im.angry.openeuicc.core import android.content.Context -import android.se.omapi.SEService import android.telephony.SubscriptionManager import android.util.Log -import im.angry.openeuicc.OpenEuiccApplication +import im.angry.openeuicc.di.AppContainer import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import java.lang.IllegalArgumentException -open class DefaultEuiccChannelManager(protected val context: Context) : EuiccChannelManager { +open class DefaultEuiccChannelManager( + protected val appContainer: AppContainer, + protected val context: Context +) : EuiccChannelManager { companion object { const val TAG = "EuiccChannelManager" } private val channels = mutableListOf() - private var seService: SEService? = null - private val lock = Mutex() protected val tm by lazy { - (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager + appContainer.telephonyManager + } + + private val euiccChannelFactory by lazy { + appContainer.euiccChannelFactory } protected open val uiccCards: Collection get() = (0.. get() = tm.uiccCardsInfoCompat - @Suppress("NAME_SHADOWING") - override fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? { - val port = port as RealUiccPortInfoCompat - if (port.card.isRemovable) { - // Attempt unprivileged (OMAPI) before TelephonyManager - // but still try TelephonyManager in case OMAPI is broken - super.tryOpenEuiccChannelUnprivileged(port)?.let { return it } - } - - if (port.card.isEuicc) { - Log.i(TAG, "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}") - try { - return TelephonyManagerChannel(port, tm) - } catch (e: IllegalArgumentException) { - // Failed - Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back") - } - } - return null - } - // Clean up channels left open in TelephonyManager // due to a (potentially) forced restart // This should be called every time the application is restarted @@ -48,7 +26,7 @@ class PrivilegedEuiccChannelManager(context: Context): DefaultEuiccChannelManage } override fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - (context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply { + appContainer.subscriptionManager.apply { findEuiccChannelBySlotBlocking(logicalSlotId)?.let { tryRefreshCachedEuiccInfo(it.cardId) } diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt index dc3921e..5158352 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -2,14 +2,19 @@ package im.angry.openeuicc.di import android.content.Context import im.angry.openeuicc.core.EuiccChannelManager +import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory import im.angry.openeuicc.core.PrivilegedEuiccChannelManager class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { override val euiccChannelManager: EuiccChannelManager by lazy { - PrivilegedEuiccChannelManager(context) + PrivilegedEuiccChannelManager(this, context) } override val uiComponentFactory by lazy { PrivilegedUiComponentFactory() } + + override val euiccChannelFactory by lazy { + PrivilegedEuiccChannelFactory(context) + } } \ No newline at end of file From 6c2b1675bd5f4ae3562d067d3a5d44cc9d507b44 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 19:35:17 -0500 Subject: [PATCH 034/472] fixup: Infinite loop in PreferenceUtils after adopting DI --- .../src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt index accbbb5..b9a854e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.map private val Context.dataStore: DataStore by preferencesDataStore(name = "prefs") val Context.preferenceRepository: PreferenceRepository - get() = (applicationContext as OpenEuiccApplication).preferenceRepository + get() = (applicationContext as OpenEuiccApplication).appContainer.preferenceRepository val Fragment.preferenceRepository: PreferenceRepository get() = requireContext().preferenceRepository From 09e19412e31330261dc1c112e1a6dac34ac9aaf6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 19:59:10 -0500 Subject: [PATCH 035/472] fix: Show less logs in UI than what we will save ...to avoid the UI getting stuck due to the sheer amount of lines. --- .../main/java/im/angry/openeuicc/ui/LogsActivity.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index 1a7e50f..70f6992 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -23,13 +23,15 @@ class LogsActivity : AppCompatActivity() { private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var scrollView: ScrollView private lateinit var logText: TextView + private lateinit var logStr: String private val saveLogs = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> if (uri == null) return@registerForActivityResult + if (!this::logStr.isInitialized) return@registerForActivityResult contentResolver.openFileDescriptor(uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { os -> - os.write(logText.text.toString().encodeToByteArray()) + os.write(logStr.encodeToByteArray()) } } } @@ -76,7 +78,12 @@ class LogsActivity : AppCompatActivity() { private suspend fun reload() = withContext(Dispatchers.Main) { swipeRefresh.isRefreshing = true - logText.text = intent.extras?.getString("log") ?: readSelfLog() + logStr = intent.extras?.getString("log") ?: readSelfLog() + + logText.text = withContext(Dispatchers.IO) { + // Limit the UI to display only 256 lines + logStr.lines().takeLast(256).joinToString("\n") + } swipeRefresh.isRefreshing = false From ca0085e14733906ad20474b8246633b91df272b3 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 4 Mar 2024 20:07:26 -0500 Subject: [PATCH 036/472] fix: Do not crash if a certificate key ID is not known Fixes #17. --- .../main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt index f3e5e90..13848b6 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -8,7 +8,7 @@ import java.security.cert.CertificateFactory const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb" private fun getCertificate(keyId: String): Certificate? = - KNOWN_CI_CERTS[keyId]?.toByteArray().let { cert -> + KNOWN_CI_CERTS[keyId]?.toByteArray()?.let { cert -> ByteArrayInputStream(cert).use { stream -> val cf = CertificateFactory.getInstance("X.509") cf.generateCertificate(stream) From 8ee3c53492a75bf3606198c7e579babf59b3acdc Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 5 Mar 2024 20:07:49 -0500 Subject: [PATCH 037/472] buildSrc: Use HEAD rev count as version code In Actions, we do not always have a checkout of the master branch. This only applies to release builds anyway. For debug builds, we always use the timestamp. --- buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt index 2c77fd3..24f0235 100644 --- a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt +++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt @@ -12,7 +12,7 @@ val Project.gitVersionCode: Int try { val stdout = ByteArrayOutputStream() exec { - commandLine("git", "rev-list", "--first-parent", "--count", "master") + commandLine("git", "rev-list", "--first-parent", "--count", "HEAD") standardOutput = stdout } stdout.toString("utf-8").trim('\n').toInt() From 124d1690abacfcf728bf7c160798f2721313c2e6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 5 Mar 2024 20:12:19 -0500 Subject: [PATCH 038/472] fix: Clear status icon when compat check items are recycled --- .../im/angry/openeuicc/ui/CompatibilityCheckActivity.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt index 0eb433a..b7c8cf8 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.children import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -52,11 +53,16 @@ class CompatibilityCheckActivity: AppCompatActivity() { inner class ViewHolder(private val root: View): RecyclerView.ViewHolder(root) { private val titleView: TextView = root.findViewById(R.id.compatibility_check_title) private val descView: TextView = root.findViewById(R.id.compatibility_check_desc) + private val statusContainer: ViewGroup = root.findViewById(R.id.compatibility_check_status_container) fun bindItem(item: CompatibilityCheck) { titleView.text = item.title descView.text = item.description + statusContainer.children.forEach { + it.visibility = View.GONE + } + when (item.state) { CompatibilityCheck.State.SUCCESS -> { root.findViewById(R.id.compatibility_check_checkmark).visibility = View.VISIBLE From 348395c48da26aae77f4cd87aff3b96dc57fa866 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 5 Mar 2024 20:14:21 -0500 Subject: [PATCH 039/472] chore: Bump lpac --- libs/lpac-jni/src/main/jni/lpac | 2 +- libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/lpac-jni/src/main/jni/lpac b/libs/lpac-jni/src/main/jni/lpac index 47f44f9..dc09c3e 160000 --- a/libs/lpac-jni/src/main/jni/lpac +++ b/libs/lpac-jni/src/main/jni/lpac @@ -1 +1 @@ -Subproject commit 47f44f911099bffdbdb4854500578ba18ab19d06 +Subproject commit dc09c3e668fc191694e73fede255a7a0a26f4988 diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index 6cdda00..503f816 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -236,7 +236,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cGetProfilesInfo(JNIEnv *env, jobject th (*env)->DeleteLocalRef(env, jinfo); }); - es10c_profile_info_free_all(info); + es10c_profile_info_list_free_all(info); return ret; } From 3a0d805eb268c42dc53d2014f0b62ec7937b45c8 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 11 Mar 2024 18:55:32 -0400 Subject: [PATCH 040/472] treewide: Nullability fixes for AOSP 14 r29 Mainly a treewide `findViewById` -> `requireViewById`, with miscellaneous fixes. --- .../openeuicc/ui/EuiccManagementFragment.kt | 16 ++++++++-------- .../java/im/angry/openeuicc/ui/LogsActivity.kt | 8 ++++---- .../java/im/angry/openeuicc/ui/MainActivity.kt | 4 ++-- .../openeuicc/ui/NotificationsActivity.kt | 10 +++++----- .../openeuicc/ui/ProfileDownloadFragment.kt | 14 +++++++------- .../openeuicc/ui/ProfileRenameFragment.kt | 6 +++--- .../im/angry/openeuicc/ui/SettingsActivity.kt | 2 +- .../angry/openeuicc/ui/SlotSelectFragment.kt | 4 ++-- .../LongSummaryPreferenceCategory.kt | 2 +- .../main/java/im/angry/openeuicc/util/Utils.kt | 4 ++-- .../openeuicc/ui/CompatibilityCheckActivity.kt | 18 +++++++++--------- .../openeuicc/service/OpenEuiccService.kt | 6 +++--- .../java/im/angry/openeuicc/ui/LuiActivity.kt | 4 ++-- .../ui/PrivilegedEuiccManagementFragment.kt | 2 +- .../angry/openeuicc/ui/SlotMappingFragment.kt | 10 +++++----- .../util/PrivilegedTelephonyCompat.kt | 6 +++--- 16 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt index d2e7caa..7564cc8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -58,9 +58,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, ): View { val view = inflater.inflate(R.layout.fragment_euicc, container, false) - swipeRefresh = view.findViewById(R.id.swipe_refresh) - fab = view.findViewById(R.id.fab) - profileList = view.findViewById(R.id.profile_list) + swipeRefresh = view.requireViewById(R.id.swipe_refresh) + fab = view.requireViewById(R.id.fab) + profileList = view.requireViewById(R.id.profile_list) return view } @@ -191,11 +191,11 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, } inner class ProfileViewHolder(private val root: View) : ViewHolder(root) { - private val iccid: TextView = root.findViewById(R.id.iccid) - private val name: TextView = root.findViewById(R.id.name) - private val state: TextView = root.findViewById(R.id.state) - private val provider: TextView = root.findViewById(R.id.provider) - private val profileMenu: ImageButton = root.findViewById(R.id.profile_menu) + private val iccid: TextView = root.requireViewById(R.id.iccid) + private val name: TextView = root.requireViewById(R.id.name) + private val state: TextView = root.requireViewById(R.id.state) + private val provider: TextView = root.requireViewById(R.id.provider) + private val profileMenu: ImageButton = root.requireViewById(R.id.profile_menu) init { iccid.setOnClickListener { diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index 70f6992..2b36468 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -39,12 +39,12 @@ class LogsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_logs) - setSupportActionBar(findViewById(R.id.toolbar)) + setSupportActionBar(requireViewById(R.id.toolbar)) supportActionBar!!.setDisplayHomeAsUpEnabled(true) - swipeRefresh = findViewById(R.id.swipe_refresh) - scrollView = findViewById(R.id.scroll_view) - logText = findViewById(R.id.log_text) + swipeRefresh = requireViewById(R.id.swipe_refresh) + scrollView = requireViewById(R.id.scroll_view) + logText = requireViewById(R.id.log_text) swipeRefresh.setOnRefreshListener { lifecycleScope.launch { diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index ca6715d..c7699e0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -36,9 +36,9 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - setSupportActionBar(findViewById(R.id.toolbar)) + setSupportActionBar(requireViewById(R.id.toolbar)) - noEuiccPlaceholder = findViewById(R.id.no_euicc_placeholder) + noEuiccPlaceholder = requireViewById(R.id.no_euicc_placeholder) tm = telephonyManager diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt index 87c5ba3..925ed84 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt @@ -37,14 +37,14 @@ class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_notifications) - setSupportActionBar(findViewById(R.id.toolbar)) + setSupportActionBar(requireViewById(R.id.toolbar)) supportActionBar!!.setDisplayHomeAsUpEnabled(true) euiccChannel = euiccChannelManager .findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!! - swipeRefresh = findViewById(R.id.swipe_refresh) - notificationList = findViewById(R.id.recycler_view) + swipeRefresh = requireViewById(R.id.swipe_refresh) + notificationList = requireViewById(R.id.recycler_view) notificationList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) @@ -118,8 +118,8 @@ class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker { @SuppressLint("ClickableViewAccessibility") inner class NotificationViewHolder(private val root: View): RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener { - private val address: TextView = root.findViewById(R.id.notification_address) - private val profileName: TextView = root.findViewById(R.id.notification_profile_name) + private val address: TextView = root.requireViewById(R.id.notification_address) + private val profileName: TextView = root.requireViewById(R.id.notification_profile_name) private lateinit var notification: LocalProfileNotificationWrapper diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt index 43fc1ee..8ab09d9 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -69,13 +69,13 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(), ): View { val view = inflater.inflate(R.layout.fragment_profile_download, container, false) - toolbar = view.findViewById(R.id.toolbar) - profileDownloadServer = view.findViewById(R.id.profile_download_server) - profileDownloadCode = view.findViewById(R.id.profile_download_code) - profileDownloadConfirmationCode = view.findViewById(R.id.profile_download_confirmation_code) - profileDownloadIMEI = view.findViewById(R.id.profile_download_imei) - profileDownloadFreeSpace = view.findViewById(R.id.profile_download_free_space) - progress = view.findViewById(R.id.progress) + toolbar = view.requireViewById(R.id.toolbar) + profileDownloadServer = view.requireViewById(R.id.profile_download_server) + profileDownloadCode = view.requireViewById(R.id.profile_download_code) + profileDownloadConfirmationCode = view.requireViewById(R.id.profile_download_confirmation_code) + profileDownloadIMEI = view.requireViewById(R.id.profile_download_imei) + profileDownloadFreeSpace = view.requireViewById(R.id.profile_download_free_space) + progress = view.requireViewById(R.id.progress) toolbar.inflateMenu(R.menu.fragment_profile_download) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 7ebac39..7987117 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -46,9 +46,9 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment ): View { val view = inflater.inflate(R.layout.fragment_profile_rename, container, false) - toolbar = view.findViewById(R.id.toolbar) - profileRenameNewName = view.findViewById(R.id.profile_rename_new_name) - progress = view.findViewById(R.id.progress) + toolbar = view.requireViewById(R.id.toolbar) + profileRenameNewName = view.requireViewById(R.id.profile_rename_new_name) + progress = view.requireViewById(R.id.progress) toolbar.inflateMenu(R.menu.fragment_profile_rename) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsActivity.kt index f5bb4d4..426c546 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsActivity.kt @@ -9,7 +9,7 @@ class SettingsActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) - setSupportActionBar(findViewById(R.id.toolbar)) + setSupportActionBar(requireViewById(R.id.toolbar)) supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportFragmentManager.beginTransaction() .replace(R.id.settings_container, SettingsFragment()) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt index 9bdcbc1..6288b0d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt @@ -39,13 +39,13 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker ): View? { val view = inflater.inflate(R.layout.fragment_slot_select, container, false) - toolbar = view.findViewById(R.id.toolbar) + toolbar = view.requireViewById(R.id.toolbar) toolbar.setTitle(R.string.slot_select) toolbar.inflateMenu(R.menu.fragment_slot_select) val adapter = ArrayAdapter(inflater.context, R.layout.spinner_item) - spinner = view.findViewById(R.id.spinner) + spinner = view.requireViewById(R.id.spinner) spinner.adapter = adapter channels.forEach { channel -> diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/preference/LongSummaryPreferenceCategory.kt b/app-common/src/main/java/im/angry/openeuicc/ui/preference/LongSummaryPreferenceCategory.kt index a35e897..dc16e15 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/preference/LongSummaryPreferenceCategory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/preference/LongSummaryPreferenceCategory.kt @@ -18,4 +18,4 @@ class LongSummaryPreferenceCategory: PreferenceCategory { summaryText.isSingleLine = false summaryText.maxLines = 10 } -} \ No newline at end of file +} diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 33699d7..175e85f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -24,7 +24,7 @@ val Context.selfAppVersion: String get() = try { val pInfo = packageManager.getPackageInfo(packageName, 0) - pInfo.versionName + pInfo.versionName!! } catch (e: PackageManager.NameNotFoundException) { throw RuntimeException(e) } @@ -91,4 +91,4 @@ suspend fun connectSEService(context: Context): SEService = suspendCoroutine { c } } } -} \ No newline at end of file +} diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt index b7c8cf8..b747bef 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/CompatibilityCheckActivity.kt @@ -24,10 +24,10 @@ class CompatibilityCheckActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_compatibility_check) - setSupportActionBar(findViewById(R.id.toolbar)) + setSupportActionBar(requireViewById(R.id.toolbar)) supportActionBar!!.setDisplayHomeAsUpEnabled(true) - compatibilityCheckList = findViewById(R.id.recycler_view) + compatibilityCheckList = requireViewById(R.id.recycler_view) compatibilityCheckList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) compatibilityCheckList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) @@ -51,9 +51,9 @@ class CompatibilityCheckActivity: AppCompatActivity() { } inner class ViewHolder(private val root: View): RecyclerView.ViewHolder(root) { - private val titleView: TextView = root.findViewById(R.id.compatibility_check_title) - private val descView: TextView = root.findViewById(R.id.compatibility_check_desc) - private val statusContainer: ViewGroup = root.findViewById(R.id.compatibility_check_status_container) + private val titleView: TextView = root.requireViewById(R.id.compatibility_check_title) + private val descView: TextView = root.requireViewById(R.id.compatibility_check_desc) + private val statusContainer: ViewGroup = root.requireViewById(R.id.compatibility_check_status_container) fun bindItem(item: CompatibilityCheck) { titleView.text = item.title @@ -65,16 +65,16 @@ class CompatibilityCheckActivity: AppCompatActivity() { when (item.state) { CompatibilityCheck.State.SUCCESS -> { - root.findViewById(R.id.compatibility_check_checkmark).visibility = View.VISIBLE + root.requireViewById(R.id.compatibility_check_checkmark).visibility = View.VISIBLE } CompatibilityCheck.State.FAILURE -> { - root.findViewById(R.id.compatibility_check_error).visibility = View.VISIBLE + root.requireViewById(R.id.compatibility_check_error).visibility = View.VISIBLE } CompatibilityCheck.State.FAILURE_UNKNOWN -> { - root.findViewById(R.id.compatibility_check_unknown).visibility = View.VISIBLE + root.requireViewById(R.id.compatibility_check_unknown).visibility = View.VISIBLE } else -> { - root.findViewById(R.id.compatibility_check_progress_bar).visibility = View.VISIBLE + root.requireViewById(R.id.compatibility_check_progress_bar).visibility = View.VISIBLE } } } diff --git a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt index 23f1444..3c9eaf2 100644 --- a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt +++ b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt @@ -104,9 +104,9 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { return GetDefaultDownloadableSubscriptionListResult(RESULT_OK, arrayOf()) } - override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult? { + override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult { Log.i(TAG, "onGetEuiccProfileInfoList slotId=$slotId") - val channel = findChannel(slotId) ?: return null + val channel = findChannel(slotId)!! val profiles = channel.lpa.profiles.operational.map { EuiccProfileInfo.Builder(it.iccid).apply { setProfileName(it.name) @@ -256,4 +256,4 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { // No-op -- we do not care return RESULT_FIRST_USER } -} \ No newline at end of file +} diff --git a/app/src/main/java/im/angry/openeuicc/ui/LuiActivity.kt b/app/src/main/java/im/angry/openeuicc/ui/LuiActivity.kt index e60dfa7..f12c9b4 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/LuiActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/LuiActivity.kt @@ -10,10 +10,10 @@ class LuiActivity : AppCompatActivity() { super.onStart() setContentView(R.layout.activity_lui) - findViewById(R.id.lui_skip).setOnClickListener { finish() } + requireViewById(R.id.lui_skip).setOnClickListener { finish() } // TODO: Deactivate LuiActivity if there is no eSIM found. // TODO: Support pre-filled download info (from carrier apps); UX - findViewById(R.id.lui_download).setOnClickListener { + requireViewById(R.id.lui_download).setOnClickListener { startActivity(Intent(this, DirectProfileDownloadActivity::class.java)) } } diff --git a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt index 78fcd4f..744acb8 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt @@ -17,7 +17,7 @@ class PrivilegedEuiccManagementFragment: EuiccManagementFragment() { override suspend fun onCreateFooterViews(parent: ViewGroup): List = if (channel.isMEP) { val view = layoutInflater.inflate(R.layout.footer_mep, parent, false) - view.findViewById