[
  {
    "path": ".commitlintrc.yml",
    "content": "extends:\n  - '@commitlint/config-conventional'\ndefaultIgnores: false\nrules:\n  #允许中文\n  subject-case: [ 0 ]\n  footer-max-line-length: [ 0 ]\n  body-max-line-length: [ 0 ]\nhelpUrl: https://www.conventionalcommits.org/zh-hans/v1.0.0/\n"
  },
  {
    "path": ".github/actions/post-build/action.yml",
    "content": "name: post-build\ndescription: '清理构建环境'\nruns:\n  using: \"composite\"\n  steps:\n    - name: stop gradle deamon for actions/cache\n      shell: bash\n      run: ./gradlew --stop\n"
  },
  {
    "path": ".github/actions/pre-build/action.yml",
    "content": "name: pre-build\ndescription: '准备构建环境'\nruns:\n  using: \"composite\"\n  steps:\n    - name: revert gradle distributionUrl in every gradle-wrapper.properties\n      shell: bash\n      run: git grep -l 'mirrors.tencent.com/gradle' -- gradle-wrapper.properties '**/gradle-wrapper.properties' | xargs sed -i 's/mirrors.tencent.com\\/gradle/services.gradle.org\\/distributions/g'\n    - name: Inject slug/short variables\n      uses: rlespinasse/github-slug-action@v3.x\n    - name: revert gradle wrapper mirror setting\n      shell: bash\n      run: echo \"DISABLE_TENCENT_MAVEN_MIRROR=true\" >> $GITHUB_ENV\n    - name: Add cmdline-tools to PATH\n      shell: bash\n      run: echo \"$ANDROID_HOME/cmdline-tools/latest/bin\" >> \"$GITHUB_PATH\"\n    - name: Install specific Android SDK platforms\n      shell: bash\n      run: sdkmanager \"platforms;android-33\"\n    - uses: actions/setup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '17'\n        cache: 'gradle'\n"
  },
  {
    "path": ".github/workflows/check-build-test.yml",
    "content": "name: Check & Build & Test\non:\n  workflow_call:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches: [ master ]\n\njobs:\n  check-commit-message:\n    name: 提交日志格式化检查\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: wagoid/commitlint-github-action@v6\n        with:\n          configFile: ./.commitlintrc.yml\n  check-code-format:\n    name: 代码格式化检查\n    runs-on: ubuntu-22.04\n    env:\n      AndroidStudioVersion: 2021.1.1.20\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: Cache android-studio\n        id: cache-android-studio\n        uses: actions/cache@v4\n        with:\n          path: android-studio\n          key: ${{ runner.os }}-android-studio--${{ env.AndroidStudioVersion }}\n      - name: download android-studio\n        if: steps.cache-android-studio.outputs.cache-hit != 'true'\n        run: |\n          wget \"https://redirector.gvt1.com/edgedl/android/studio/ide-zips/$AndroidStudioVersion/android-studio-$AndroidStudioVersion-linux.tar.gz\"\n          tar -xvzf \"android-studio-$AndroidStudioVersion-linux.tar.gz\"\n          rm -rf \"android-studio-$AndroidStudioVersion-linux.tar.gz\"\n      - name: use android-studio format all files\n        run: ./android-studio/bin/format.sh -s .idea/codeStyles/Project.xml -r -m \\*.java,\\*.kt,\\*.xml .\n      - name: show diff for files not formated\n        run: |\n          if ! git diff --quiet; then\n            git diff --exit-code\n          fi\n  build-sdk:\n    needs: [ check-commit-message, check-code-format ]\n    name: 构建SDK\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: buildSdk\n        run: ./gradlew buildSdk -S\n      - name: post-build\n        uses: ./.github/actions/post-build\n  build-sample-maven:\n    needs: [ check-commit-message, check-code-format ]\n    name: 构建maven依赖SDK的sample\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: '11'\n          cache: 'gradle'\n      - name: build sample/maven/host-project\n        working-directory: projects/sample/maven/host-project\n        run: ./gradlew assemble\n      - name: build sample/maven/manager-project\n        working-directory: projects/sample/maven/manager-project\n        run: ./gradlew assemble\n      - name: build sample/maven/plugin-project\n        working-directory: projects/sample/maven/plugin-project\n        run: ./gradlew assemble\n      - name: post-build\n        uses: ./.github/actions/post-build\n  build-all:\n    needs: build-sdk\n    name: 构建所有源码\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: buildSdk\n        run: ./gradlew build\n      - name: post-build\n        uses: ./.github/actions/post-build\n  test-agp-compatibility:\n    needs: build-sdk\n    name: AGP兼容性自动化测试\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: '17'\n          cache: 'gradle'\n      - name: JDK17环境下AGP测试\n        working-directory: projects/test/gradle-plugin-agp-compat-test\n        run: ./test_JDK17.sh\n      - uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: '11'\n          cache: 'gradle'\n      - name: JDK11环境下AGP测试\n        working-directory: projects/test/gradle-plugin-agp-compat-test\n        run: ./test_JDK11.sh\n      - name: post-build\n        uses: ./.github/actions/post-build\n  test-sdk-jvm:\n    needs: build-sdk\n    name: 自动化测试-JVM部分\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: jvmTestSdk\n        run: ./gradlew jvmTestSdk -S\n      - name: post-build\n        uses: ./.github/actions/post-build\n  test-sdk-avd:\n    needs: build-sdk\n    name: 自动化测试-AVD部分\n    runs-on: ubuntu-22.04\n    strategy:\n      matrix:\n        include:\n          - api-level: 16 #16是最低支持的API\n            arch: x86\n            target: default\n          - api-level: 28 #28是项目长期使用的测试API\n            arch: x86\n            target: default\n          - api-level: 34\n            arch: x86_64\n            target: google_apis\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: Enable KVM\n        run: |\n          echo 'KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0666\", OPTIONS+=\"static_node=kvm\"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules\n          sudo udevadm control --reload-rules\n          sudo udevadm trigger --name-match=kvm\n      - name: run AVD tests\n        uses: reactivecircus/android-emulator-runner@v2\n        with:\n          api-level: ${{ matrix.api-level }}\n          target: ${{ matrix.target }}\n          arch: ${{ matrix.arch }}\n          profile: pixel_xl\n          force-avd-creation: false\n          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none\n          disable-animations: true\n          script: ./gradlew androidTestSdk\n      - name: post-build\n        uses: ./.github/actions/post-build\n  test-sdk-avd-target34:\n    needs: build-sdk\n    name: 自动化测试-target 34的冒烟测试\n    runs-on: ubuntu-22.04\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: Enable KVM\n        run: |\n          echo 'KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0666\", OPTIONS+=\"static_node=kvm\"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules\n          sudo udevadm control --reload-rules\n          sudo udevadm trigger --name-match=kvm\n      - name: run AVD tests\n        uses: reactivecircus/android-emulator-runner@v2\n        with:\n          api-level: 34\n          target: google_apis\n          arch: x86_64\n          profile: pixel_xl\n          force-avd-creation: false\n          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none\n          disable-animations: true\n          script: ./gradlew :test-dynamic-host:connectedTarget34DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.tencent.shadow.test.cases.plugin_main.ApplicationContextSubDirTest#testGetDatabasePath\n      - name: post-build\n        uses: ./.github/actions/post-build\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Publish release\n\non:\n  release:\n    types:\n      - published #https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release\n\njobs:\n  publish:\n    needs: check-build-test\n    runs-on: ubuntu-22.04\n    env:\n      DISABLE_TENCENT_MAVEN_MIRROR: true\n      PUBLISH_RELEASE: true\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    steps:\n      - name: checkout\n        uses: actions/checkout@v4\n      - name: pre-build\n        uses: ./.github/actions/pre-build\n      - name: publish\n        run: ./gradlew publish\n      - name: post-build\n        uses: ./.github/actions/post-build\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n!.idea\n.idea/*\n!.idea/codeStyles\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.gradletasknamecache\n"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n    <code_scheme name=\"Project\" version=\"173\">\n        <JetCodeStyleSettings>\n            <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n        </JetCodeStyleSettings>\n        <codeStyleSettings language=\"XML\">\n            <option name=\"FORCE_REARRANGE_MODE\" value=\"1\" />\n            <indentOptions>\n                <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n            </indentOptions>\n            <arrangement>\n                <rules>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>xmlns:android</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>^$</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>xmlns:.*</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>^$</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                            <order>BY_NAME</order>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>.*:id</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n                                    </XML_NAMESPACE>\n                                </AND>\n                            </match>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>.*:name</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n                                    </XML_NAMESPACE>\n                                </AND>\n                            </match>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>name</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>^$</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>style</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>^$</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>.*</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>^$</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                            <order>BY_NAME</order>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>.*</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>http://schemas.android.com/apk/res/android\n                                    </XML_NAMESPACE>\n                                </AND>\n                            </match>\n                            <order>ANDROID_ATTRIBUTE_ORDER</order>\n                        </rule>\n                    </section>\n                    <section>\n                        <rule>\n                            <match>\n                                <AND>\n                                    <NAME>.*</NAME>\n                                    <XML_ATTRIBUTE />\n                                    <XML_NAMESPACE>.*</XML_NAMESPACE>\n                                </AND>\n                            </match>\n                            <order>BY_NAME</order>\n                        </rule>\n                    </section>\n                </rules>\n            </arrangement>\n        </codeStyleSettings>\n        <codeStyleSettings language=\"kotlin\">\n            <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n        </codeStyleSettings>\n    </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n    <state>\n        <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n    </state>\n</component>"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n我们非常欢迎您向Tencent Shadow提交Issue或Pull Request。\n\n# Issue\n在Tencent Shadow开源的初期，我们会密切关注所有Issue反馈。晚些时候再根据反馈的情况制定Issue模板。\n\n反馈问题时，请Fork Shadow的代码库到自己的名下。新建分支，添加可以复现问题的最小改动，提交后push到Github上。然后在Issue单中附上你的代码库地址和分支名即可。\n\n```sh\ngit clone https://github.com/Tencent/Shadow.git //大概之前你已经这样clone过Shadow的代码库了\ncd shadow //切换到你clone的shadow目录\ngit remote add your_name https://github.com/<your_name>/Shadow.git //把你fork的版本库添加成一个远端\ngit fetch --all //更新所有远端的代码\ngit checkout -b new_branch_name origin/dev // 基于Shadow代码库的dev分支新建一个分支\n//加上你复现问题的修改\ngit commit\ngit push -u your_name  //推送new_branch_name分支到你fork的版本库\n```\n然后你的分支地址应该类似：`https://github.com/<your_name>/Shadow/tree/new_branch_name`\n\n其他人可以用这样的命令获取到你的分支，看到你的提交做了哪些改动，运行并Debug。\n```sh\ncd shadow //切换到shadow目录\ngit fetch https://github.com/<your_name>/Shadow.git new_branch_name\ngit checkout -b new_branch_name FETCH_HEAD\n```\n\n# Pull Request\n由于PR会修改代码，因此即便是在开源初期，我们也会对PR谨慎处理。\n\n请注意以下问题：\n\n1. 不要提交无意义改动。\n1. 除非是提交复现问题的测试用例，请确保`gradlew testSdk`构建成功（需要连接Android设备）\n1. 测试机需要至少有API 28，API 19两种机器，以保证ART和Dalvik虚拟机都能正常工作。\n1. 尽量原子化的提交，配有较为清晰的提交信息。\n\n我们会根据大家的PR再调整PR的要求的。\n\n# 开发指引\n\n## Debug编译期代码(Gradle插件、Transform等)\n\nShadow的`coding`和`core.gradle-plugin`、`core.manifest-parser`、`core.transform`,`core.transform-kit`\n等模块都是在插件工程的编译期执行的。如果需要Debug它们，需要额外的配置。\n\n1. 添加`Remote JVM Debug` Configuration。在Android Studio的`Run`菜单中找到`Edit Configuration`。 点\"＋\"（Add New\n   Configuration），选择`Remote JVM Debug`，配置参数一般采用默认值不用修改。\n   `Name`可以任意修改成方便识别的名字，稍后在工具栏执行时选择。 复制`Command line arguments for remote JVM`。\n   一般的默认值是:`-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005`。\n   将这行复制到`gradle.properties`中的`org.gradle.jvmargs=-Xmx4096m`后面作为更多的参数，注意加上空格。 然后将其中的`suspend=n`\n   改为`suspend=y`，表示让JVM启动Gradle时等待Debugger连接，再继续执行。\n\n2. 终止正在运行的Gradle Daemon。在命令行执行`./gradlew --stop`，终止掉没有采用新参数的JVM进程。\n\n3. Debug编译期代码。通过`./gradlew`或者Android Studio的Gradle sync，或运行`sample-host`等任务， 都会在一启动时因为前面的`suspend=y`\n   卡住。这时再选择刚刚添加的`Remote JVM Debug` Configuration， 点击`Debug`执行按钮，即可连接上Gradle JVM。如果在`ShadowPlugin`\n   或者某个Transform代码中设置了断点， 就会正常在断点处暂停。 注意选择`Remote JVM Debug` Configuration的位置同选择`sample-host`\n   等模块在同一个菜单中。 并且Android Studio可以同时执行多个Configuration，先运行`sample-host`， 再Debug `Remote JVM Debug`\n   Configuration是没有问题的。\n\n4. 在其他不是Shadow源码工程中，也可以同样设置`gradle.properties`参数，在其中执行Gradle任务。\n   然后切换到Shadow源码工程中执行Debug `Remote JVM Debug` Configuration， 也可以Debug Shadow在其他工程中的编译期代码执行情况。\n\n5. 还原。回退对`gradle.properties`的修改，然后执行`./gradlew --stop`。以上所有改动的作用即可恢复。\n\n## 虚拟机\n\n启动虚拟机\n\n```shell\n~/Library/Android/sdk/emulator/emulator @Pixel_XL_API_28 -noaudio -no-boot-anim -wipe-data -no-snapstorage\n```\n\n其中`Pixel_XL_API_28`来自：\n\n```shell\n~/Library/Android/sdk/emulator/emulator -list-avds\n```\n\n`-noaudio`可以避免耳机切换到通话模式。\n`-wipe-data -no-snapstorage`使得虚拟机完全恢复到新建状态。\n`-no-boot-anim`稍微加快点启动。\n\n## 启动指定自动化测试用例\n\n随着Android Studio更新，在#1263 解决之前，只能通过命令行执行自动化测试。\n如下命令，传入类名#方法可以指定单个方法。只传入类名可以测试整个类。\n如果测试方法是抽象类中的，需要传入一个具体的实现类。\n\n```shell\n./gradlew :test-dynamic-host:connectedTarget28DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.tencent.shadow.test.cases.plugin_main.ApplicationContextSubDirTest#testGetDatabasePath\n```\n\n## 清理工作区\n\n由于复合构建的存在，Gradle clean任务不能总是很好的完成清理工作区的目的。\n\n```shell\ngit clean -fdx -e .idea -e local.properties \n```\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of THL A29 Limited nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "PRIVACY.md",
    "content": "# Tencent Shadow SDK个人信息保护规则\n_生效日期：2022年4月6日_\n\n## 引言\n\nTencent Shadow SDK (以下简称“SDK产品”)由深圳市腾讯计算机系统有限公司(以下简称“我们”)开发, 公司注册地为深圳市南山区粤海街道麻岭社区科技中一路腾讯大厦35层。\n\n**我们在此特别声明:**\n\n**1. 本SDK产品功能的实现将不需要收集、获取、传输、分享或者使用终端用户的任何个人信息。**\n\n**2. 我们不会因为开发者适配、集成和装载本SDK产品而向其提供、传输或共享任何的个人信息。**\n\n**3. 如果开发者因提供其产品或服务而需要处理终端用户的个人信息, 由开发者独自承担相应的法律责任。**\n\n**4. 请终端用户注意, 在开发者将本SDK产品适配、集成或装载到开发者产品或服务前, 我们已经要求相关开发者仔细阅读我们在官网公示的相关服务协议、本规则及开发者合规指南(或具有同样性质的相关法律文件), 并已经要求开发者依据开发者的产品收集使用个人信息的情况进行合规自查。**\n\n**5. 如果我们更新、改进或修改了本SDK产品, 并因此导致我们需要处理终端用户的个人信息的, 我们将会依据适用法律的要求对本规则进行修订, 并将修订后的内容及时告知开发者和终端用户, 我们将要求开发者适时更新其隐私政策,并以弹框形式通知终端用户并且获得其同意。**\n\n**6. 对于本规则的任何内容存在疑问的, 可以通过如下的方式与我们取得联系:**\n\n(1) 通过 https://kf.qq.com/ 与我们联系进行在线咨询;\n(2) 发送邮件至 Dataprivacy@tencent.com ;  \n(3) 邮寄信件至：中国广东省深圳市南山区海天二路33号腾讯滨海大厦 数据隐私保护部(收)邮编：518054。\n"
  },
  {
    "path": "README.md",
    "content": "# Shadow\n\n![Android CI](https://github.com/Tencent/Shadow/workflows/Android%20CI/badge.svg?event=push)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)\n\n## 介绍\nShadow是一个腾讯自主研发的Android插件框架，经过线上亿级用户量检验。\nShadow不仅开源分享了插件技术的关键代码，还完整的分享了上线部署所需要的所有设计。\n\n与市面上其他插件框架相比，Shadow主要具有以下特点：\n\n* **复用独立安装App的源码**：插件App的源码原本就是可以正常安装运行的。\n* **零反射无Hack实现插件技术**：从理论上就已经确定无需对任何系统做兼容开发，更无任何隐藏API调用，和Google限制非公开SDK接口访问的策略完全不冲突。\n* **全动态插件框架**：一次性实现完美的插件框架很难，但Shadow将这些实现全部动态化起来，使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制。\n* **宿主增量极小**：得益于全动态实现，真正合入宿主程序的代码量极小（15KB，160方法数左右）。\n* **Kotlin实现**：core.loader，core.transform核心代码完全用Kotlin实现，代码简洁易维护。\n\n### 支持特性\n* 四大组件\n* Fragment（代码添加和Xml添加）\n* DataBinding（无需特别支持，但已验证可正常工作）\n* 跨进程使用插件Service\n* 自定义Theme\n* 插件访问宿主类\n* So加载\n* 分段加载插件（多Apk分别加载或多Apk以此依赖加载）\n* 一个Activity中加载多个Apk中的View\n* 等等……\n\n## 编译与开发环境\n\n### 环境准备\n建议直接用最新的稳定版本Android Studio打开工程。目前项目已适配`Android Studio Arctic Fox | 2020.3.1`，\n低版本的Android Studio可能因为Gradle版本过高而无法正常打开项目。\n\n然后在IDE中选择`sample-app`或`sample-host`模块直接运行，分别体验同一份代码在正常安装情况下和插件情况下的运行情况。\n\n![选择sample-host直接运行](pics/run-sample-host-in-ide.png)\n\nShadow的所有代码都位于`projects`目录下的3个目录，分别是：\n\n* `sdk`包含SDK的所有代码\n* `test`包含SDK的自动化测试代码\n* `sample`包含演示代码\n\n其中`sample`应该是大家体验Shadow的最佳环境。\n详见`sample`目录中的[README](projects/sample/README.md)介绍。\n\n### 兼容性\n\nShadow项目有较为完善的自动化测试，因此最新代码对外部环境的版本兼容性可以参考自动化测试的配置。\n\n* [pr-check.yml](.github/workflows/pr-check.yml) 虚拟机自动化测试，包含Android测试机版本和编译环境JDK等版本。\n* [pr-check-gradle-plugin.yml](.github/workflows/pr-check-gradle-plugin.yml) AGP兼容性测试。\n  其中指向的[test_JDK17.sh](projects/test/gradle-plugin-agp-compat-test/test_JDK17.sh)和\n  [test_JDK11.sh](projects/test/gradle-plugin-agp-compat-test/test_JDK11.sh)中定义了被测试的AGP版本。\n\n## 自己写的测试代码出错？\n\n以我们多年的插件环境下业务开发经验，插件框架是不可能一步到位实现完美的。\n因此，我们相信大部分业务在接入时都是需要一定的二次开发工作。\nShadow现有的代码满足的是我们自己的业务现在的需求。得益于全动态的设计，\n插件框架和插件本身都是动态发布的，插件包里既有插件代码也有插件框架代码，\n所以可以根据新版本插件的需要同时开发插件框架。\n\n例如，ShadowActivity没有实现全所有Activity方法，你写的测试代码可能用到了，\n就会出现Method Not Found错误，只需要在ShadowActivity中实现对应方法就可以了。\n大部分方法的实现都只是需要简单的转调就能工作正常。\n\n如果遇到不会实现的功能，可以提Issue。最好附上测试代码。\n\n## 后续开发\n* 原理与设计说明文档\n* 多插件支持的演示工程\n* 自动化测试用例补充\n* 开源包含下载能力的manager实现\n\n## 贡献代码\n\n详见[CONTRIBUTING.md](CONTRIBUTING.md)\n\n## 许可协议\n\nTencent Shadow采用`BSD 3-Clause License`，详见[LICENSE](LICENSE.txt)。\n\n## 个人信息保护规则声明\n\n详见[PRIVACY.md](PRIVACY.md)\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\n//buildscript不能从其他gradle文件中apply，所以这段buildscript脚本存在于多个子构建中。\n//请更新buildscript时同步更新。\nbuildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = 'buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        classpath 'com.tencent.shadow.coding:aar-to-jar-plugin'\n        classpath 'com.tencent.shadow.coding:common-jar-settings'\n    }\n}\napply from: 'buildScripts/gradle/common.gradle'\n\napply from: \"buildScripts/gradle/maven.gradle\"\n\napply from: \"buildScripts/gradle/fix_issue_1263.gradle\"\n"
  },
  {
    "path": "buildScripts/gradle/common.gradle",
    "content": "def gitShortRev() {\n    def gitCommit = \"\"\n    def proc = \"git rev-parse --short HEAD\".execute()\n    proc.in.eachLine { line -> gitCommit = line }\n    proc.err.eachLine { line -> println line }\n    proc.waitFor()\n    return gitCommit\n}\n\nallprojects {\n    def versionName, versionSuffix\n    if (\"${System.env.CI}\".equalsIgnoreCase(\"true\")) {\n        versionName = System.getenv(\"GITHUB_REF_SLUG\")\n    } else {\n        versionName = project.VERSION_NAME\n    }\n\n    if (\"${System.env.PUBLISH_RELEASE}\".equalsIgnoreCase(\"true\")) {\n        versionSuffix = \"\"\n    } else if (\"${System.env.CI}\".equalsIgnoreCase(\"true\")) {\n        versionSuffix = \"-${System.env.GITHUB_SHA_SHORT}-SNAPSHOT\"\n    } else {\n        versionSuffix = \"-${gitShortRev()}-SNAPSHOT\"\n    }\n    ext.ARTIFACT_VERSION = versionName + versionSuffix\n    ext.TEST_HOST_APP_APPLICATION_ID = 'com.tencent.shadow.test.hostapp'\n    ext.SAMPLE_HOST_APP_APPLICATION_ID = 'com.tencent.shadow.sample.host'\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n}\n"
  },
  {
    "path": "buildScripts/gradle/fix_issue_1263.gradle",
    "content": "/**\n * 这个脚本通过hook create*ApkListingFileRedirect任务，\n * 在它执行完成后，即生成了apk_ide_redirect_file，也应该生成了apk之后，\n * 补充一个复制build/intermediates/apk到build/outputs/apk的操作。\n *\n * 采用这个修复方式是因为Shadow的打包代码设计不是很合理，难以通过少量改动，\n * 保证引用项目不引入任何兼容性问题。\n *\n * 详见issue #1263\n */\nbuildscript {\n    dependencies {\n        classpath files(rootProject.buildscript.configurations.classpath)\n    }\n}\ndef taskList = [\n        \":sample-loader:createDebugApkListingFileRedirect\",\n        \":sample-loader:createReleaseApkListingFileRedirect\",\n        \":sample-runtime:createDebugApkListingFileRedirect\",\n        \":sample-runtime:createReleaseApkListingFileRedirect\",\n        \":sample-manager:createDebugApkListingFileRedirect\",\n        \":sample-manager:createReleaseApkListingFileRedirect\",\n        \":sample-app:createPluginDebugApkListingFileRedirect\",\n        \":sample-app:createPluginReleaseApkListingFileRedirect\",\n        \":sample-base:createPluginDebugApkListingFileRedirect\",\n        \":sample-base:createPluginReleaseApkListingFileRedirect\",\n\n        \":test-dynamic-loader:createDebugApkListingFileRedirect\",\n        \":test-dynamic-loader:createReleaseApkListingFileRedirect\",\n        \":test-dynamic-runtime:createDebugApkListingFileRedirect\",\n        \":test-dynamic-runtime:createReleaseApkListingFileRedirect\",\n        \":test-dynamic-manager:createDebugApkListingFileRedirect\",\n        \":test-dynamic-manager:createReleaseApkListingFileRedirect\",\n        \":plugin-service-for-host:createPluginDebugApkListingFileRedirect\",\n        \":plugin-service-for-host:createPluginReleaseApkListingFileRedirect\",\n        \":test-plugin-androidx-cases:createPluginDebugApkListingFileRedirect\",\n        \":test-plugin-androidx-cases:createPluginReleaseApkListingFileRedirect\",\n        \":test-plugin-general-cases:createPluginDebugApkListingFileRedirect\",\n        \":test-plugin-general-cases:createPluginReleaseApkListingFileRedirect\",\n\n        \":sample-hello-apk:createDebugApkListingFileRedirect\",\n        \":sample-hello-apk:createReleaseApkListingFileRedirect\",\n]\n\nafterEvaluate {\n    taskList.forEach {\n        def t = tasks.findByPath(it)\n        copyApkAfterTask(t)\n    }\n}\n\ndef copyApkAfterTask(t) {\n    t.doLast {\n        def redirectFile = t.getOutputs().getFiles().singleFile\n        def listingFile = redirectFile.readLines().get(1).replaceFirst(\"listingFile=\", \"\")\n        def metadataFile = new File(redirectFile.parentFile, listingFile)\n        def metadata = new org.json.JSONObject(metadataFile.text)\n        def outputFile = metadata.getJSONArray(\"elements\").getJSONObject(0).getString(\"outputFile\")\n        def apkFile = new File(metadataFile.parentFile, outputFile)\n        def testRelativePath = redirectFile.relativePath(apkFile)\n        def needCopy = !testRelativePath.matches(\"^(\\\\.\\\\.${File.separatorChar})+outputs${File.separatorChar}.+\")\n        if (needCopy) {\n            def matchPath = new File(\"/build/intermediates\").toPath().toString()\n            def intermediatesDir = new File(apkFile.toPath().normalize().toString().find(\"^.+?$matchPath\"))\n            def outputsDir = new File(intermediatesDir.parentFile, \"outputs\")\n            def r = copy {\n                from intermediatesDir\n                into outputsDir\n                include 'apk/**'\n            }\n            if (r.didWork) {\n                getLogger().info(\"copy apk from ${intermediatesDir.path} to ${outputsDir.path}\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "buildScripts/gradle/maven.gradle",
    "content": "task buildSdk() {\n    dependsOn gradle.includedBuild('core').task(':gradle-plugin:assemble')\n    dependsOn gradle.includedBuild('core').task(':manifest-parser:assemble')\n    dependsOn gradle.includedBuild('core').task(':common:assemble')\n    dependsOn gradle.includedBuild('core').task(':loader:assemble')\n    dependsOn gradle.includedBuild('core').task(':manager:assemble')\n    dependsOn gradle.includedBuild('core').task(':runtime:assemble')\n    dependsOn gradle.includedBuild('core').task(':activity-container:assemble')\n    dependsOn gradle.includedBuild('core').task(':transform-kit:assemble')\n    dependsOn gradle.includedBuild('core').task(':transform-kit:testJar')\n    dependsOn gradle.includedBuild('core').task(':transform:assemble')\n    dependsOn gradle.includedBuild('core').task(':load-parameters:assemble')\n    dependsOn gradle.includedBuild('core').task(':utils:assemble')\n\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-apk:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-host:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader-impl:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-manager:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-host-multi-loader-ext:assemble')\n    dependsOn gradle.includedBuild('dynamic').task(':dynamic-manager-multi-loader-ext:assemble')\n}\n\ntask jvmTestSdk() {\n    dependsOn gradle.includedBuild('coding').task(':test')\n    dependsOn gradle.includedBuild('core').task(':test')\n    dependsOn gradle.includedBuild('dynamic').task(':test')\n    dependsOn ':common-jar-settings-test:testJavacBootclasspath'\n}\n\ntask androidTestSdk() {\n    dependsOn gradle.includedBuild('core').task(':manager-db-test:connectedDebugAndroidTest')\n    dependsOn ':test-dynamic-host:connectedTarget28DebugAndroidTest'\n}\n\ntask testSdk() {\n    dependsOn jvmTestSdk\n    dependsOn androidTestSdk\n}\n\napply plugin: 'maven-publish'\n\nstatic def getDependencyNode(scope, groupId, artifactId, version) {\n    Node node = new Node(null, 'dependency')\n    node.appendNode('groupId', groupId)\n    node.appendNode('artifactId', artifactId)\n    node.appendNode('version', version)\n    node.appendNode('scope', scope)\n    return node\n}\n\ndef gitShortRev() {\n    def gitCommit = \"\"\n    def proc = \"git rev-parse --short HEAD\".execute()\n    proc.in.eachLine { line -> gitCommit = line }\n    proc.err.eachLine { line -> println line }\n    proc.waitFor()\n    return gitCommit\n}\n\ndef setScm(scm) {\n    scm.appendNode('connection', \"https://github.com/${System.getenv(\"GITHUB_ACTOR\")}/Shadow.git\")\n\n    def commit\n    if (\"${System.env.CI}\".equalsIgnoreCase(\"true\")) {\n        commit = System.getenv(\"GITHUB_SHA\")\n    } else {\n        commit = gitShortRev()\n    }\n    scm.appendNode('url', \"https://github.com/${System.getenv(\"GITHUB_ACTOR\")}/Shadow/commit/$commit\")\n}\n\ndef setGeneratePomFileAndDepends(publicationName) {\n    model {\n        tasks.\"generatePomFileFor${publicationName.capitalize()}Publication\" {\n            destination = file(\"$buildDir/pom/$publicationName-pom.xml\")\n            dependsOn(buildSdk)\n        }\n    }\n}\n\ndef sourceJar(String name, String path) {\n    return tasks.create(\"source${name.capitalize()}Jar\", Jar) {\n        group = \"publishing\"\n        description = \"package ${name} source to jar\"\n        from \"$path/src/main/java\"\n        from \"$path/src/main/kotlin\"\n        destinationDir = file(\"$path/build/libs/\")\n        classifier = 'sources'\n    }\n}\n\ndef publicationVersion = project.ARTIFACT_VERSION\ndef coreGroupId = 'com.tencent.shadow.core'\ndef corePath = 'projects/sdk/core'\ndef dynamicGroupId = 'com.tencent.shadow.dynamic'\ndef dynamicPath = 'projects/sdk/dynamic'\n\ntask getPublicationVersion() {\n    doLast {\n        println \"publicationVersion:$publicationVersion\"\n    }\n}\n\npublishing {\n    publications {\n        gradlePlugin(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'gradle-plugin'\n            version publicationVersion\n            artifact(\"$corePath/gradle-plugin/build/libs/gradle-plugin.jar\")\n            artifact sourceJar(\"gradlePlugin\", \"$corePath/gradle-plugin\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version))\n                dependencies.append(getDependencyNode('compile', 'com.googlecode.json-simple', 'json-simple', json_simple_version))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'transform-kit', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'transform', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'activity-container', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'manifest-parser', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        // Plugin Marker Artifacts\n        // https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers\n        pluginMarker(MavenPublication) {\n            def pluginId = 'com.tencent.shadow.plugin'\n            groupId pluginId\n            artifactId pluginId + '.gradle.plugin'\n            version publicationVersion\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'gradle-plugin', publicationVersion))\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        manifestParser(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'manifest-parser'\n            version publicationVersion\n            artifact(\"$corePath/manifest-parser/build/libs/manifest-parser.jar\")\n            artifact sourceJar(\"manifestParser\", \"$corePath/manifest-parser\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version))\n                dependencies.append(getDependencyNode('compile', 'com.squareup', 'javapoet', javapoet_version))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        common(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'common'\n            version publicationVersion\n            artifact(\"$corePath/common/build/libs/common.jar\")\n            artifact sourceJar(\"common\", \"$corePath/common\")\n\n            pom.withXml {\n                def root = asNode()\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        loadParameters(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'load-parameters'\n            version publicationVersion\n            artifact(\"$corePath/load-parameters/build/libs/load-parameters.jar\")\n            artifact sourceJar(\"loadParameters\", \"$corePath/load-parameters\")\n\n            pom.withXml {\n                def root = asNode()\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        coreLoader(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'loader'\n            version publicationVersion\n            artifact(\"$corePath/loader/build/libs/loader.jar\")\n            artifact sourceJar(\"loader\", \"$corePath/loader\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'activity-container', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'load-parameters', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        coreManager(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'manager'\n            version publicationVersion\n            artifact(\"$corePath/manager/build/libs/manager.jar\")\n            artifact sourceJar(\"manager\", \"$corePath/manager\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'load-parameters', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'utils', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        runtime(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'runtime'\n            version publicationVersion\n            artifact(\"$corePath/runtime/build/libs/runtime.jar\")\n            artifact sourceJar(\"runtime\", \"$corePath/runtime\")\n\n            pom.withXml {\n                def root = asNode()\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        activityContainer(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'activity-container'\n            version publicationVersion\n            artifact(\"$corePath/activity-container/build/libs/activity-container.jar\")\n            artifact sourceJar(\"activity-container\", \"$corePath/activity-container\")\n\n            pom.withXml {\n                def root = asNode()\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        transformKit(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'transform-kit'\n            version publicationVersion\n            artifact(\"$corePath/transform-kit/build/libs/transform-kit.jar\")\n            artifact sourceJar(\"transformKit\", \"$corePath/transform-kit\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version))\n                dependencies.append(getDependencyNode('compile', 'org.javassist', 'javassist', javassist_version))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        transformKitTest(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'transform-kit-test'\n            version publicationVersion\n            artifact(\"$corePath/transform-kit/build/libs/test-transform-kit.jar\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'junit', 'junit', junit_version))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        transform(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'transform'\n            version publicationVersion\n            artifact(\"$corePath/transform/build/libs/transform.jar\")\n            artifact sourceJar(\"transform\", \"$corePath/transform\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'transform-kit', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        dynamicApk(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'apk'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-apk/build/libs/dynamic-apk.jar\")\n            artifact sourceJar(\"dynamicApk\", \"$dynamicPath/dynamic-apk\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        dynamicHost(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'host'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-host/build/libs/dynamic-host.jar\")\n            artifact sourceJar(\"dynamicHost\", \"$dynamicPath/dynamic-host\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'apk', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'utils', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        dynamicHostMultiLoaderExt(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'host-multi-loader-ext'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-host-multi-loader-ext/build/libs/dynamic-host-multi-loader-ext.jar\")\n            artifact sourceJar(\"dynamicHostMultiLoaderExt\", \"$dynamicPath/dynamic-host-multi-loader-ext\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'host', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        dynamicLoader(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'loader'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-loader/build/libs/dynamic-loader.jar\")\n            artifact sourceJar(\"dynamicLoader\", \"$dynamicPath/dynamic-loader\")\n\n            pom.withXml {\n                def root = asNode()\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        dynamicLoaderImpl(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'loader-impl'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-loader-impl/build/libs/dynamic-loader-impl.jar\")\n            artifact sourceJar(\"dynamicLoaderImpl\", \"$dynamicPath/dynamic-loader-impl\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version))\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'loader', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'activity-container', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host', publicationVersion))\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n        dynamicManager(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'manager'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-manager/build/libs/dynamic-manager.jar\")\n            artifact sourceJar(\"dynamicManager\", \"$dynamicPath/dynamic-manager\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'manager', publicationVersion))\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        dynamicManagerMultiLoaderExt(MavenPublication) {\n            groupId dynamicGroupId\n            artifactId 'manager-multi-loader-ext'\n            version publicationVersion\n            artifact(\"$dynamicPath/dynamic-manager-multi-loader-ext/build/libs/dynamic-manager-multi-loader-ext.jar\")\n            artifact sourceJar(\"dynamicManagerMultiLoaderExt\", \"$dynamicPath/dynamic-manager-multi-loader-ext\")\n\n            pom.withXml {\n                def root = asNode()\n                def dependencies = root.appendNode('dependencies')\n                dependencies.append(getDependencyNode('compile', coreGroupId, 'manager', publicationVersion))\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion))\n                dependencies.append(getDependencyNode('compile', dynamicGroupId, 'manager', publicationVersion))\n                dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion))\n                dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host-multi-loader-ext', publicationVersion))\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n\n        coreUtils(MavenPublication) {\n            groupId coreGroupId\n            artifactId 'utils'\n            version publicationVersion\n            artifact(\"$corePath/utils/build/libs/utils.jar\")\n            artifact sourceJar(\"utils\", \"$corePath/utils\")\n\n            pom.withXml {\n                def root = asNode()\n\n                def scm = root.appendNode('scm')\n                setScm(scm)\n            }\n        }\n    }\n    repositories {\n        def useLocalCredential = false\n        Properties properties = new Properties()\n        def propertiesFile = project.rootProject.file('local.properties')\n        if (propertiesFile.exists()) {\n            properties.load(propertiesFile.newDataInputStream())\n\n            if (\"${properties.getProperty('gpr.local')}\".equalsIgnoreCase('true')) {\n                def user = properties.getProperty('gpr.user')\n                def key = properties.getProperty('gpr.key')\n                maven {\n                    name = \"GitHubPackages\"\n                    credentials {\n                        username = user\n                        password = key\n                    }\n                    url \"https://maven.pkg.github.com/${user}/shadow\"\n                }\n\n                useLocalCredential = true\n            }\n        }\n\n        if (!useLocalCredential && \"${System.env.CI}\".equalsIgnoreCase(\"true\")) {\n            maven {\n                name = \"GitHubPackages\"\n                credentials {\n                    username = System.getenv(\"GITHUB_ACTOR\")\n                    password = System.getenv(\"GITHUB_TOKEN\")\n                }\n                url \"https://maven.pkg.github.com/\" + \"${System.env.GITHUB_REPOSITORY}\".toLowerCase()\n            }\n        } else {\n            mavenLocal()\n        }\n    }\n}\n\nsetGeneratePomFileAndDepends('gradlePlugin')\nsetGeneratePomFileAndDepends('manifestParser')\nsetGeneratePomFileAndDepends('common')\nsetGeneratePomFileAndDepends('loadParameters')\nsetGeneratePomFileAndDepends('coreLoader')\nsetGeneratePomFileAndDepends('coreManager')\nsetGeneratePomFileAndDepends('coreUtils')\nsetGeneratePomFileAndDepends('runtime')\nsetGeneratePomFileAndDepends('activityContainer')\nsetGeneratePomFileAndDepends('transformKit')\nsetGeneratePomFileAndDepends('transformKitTest')\nsetGeneratePomFileAndDepends('transform')\nsetGeneratePomFileAndDepends('dynamicApk')\nsetGeneratePomFileAndDepends('dynamicHost')\nsetGeneratePomFileAndDepends('dynamicHostMultiLoaderExt')\nsetGeneratePomFileAndDepends('dynamicLoader')\nsetGeneratePomFileAndDepends('dynamicLoaderImpl')\nsetGeneratePomFileAndDepends('dynamicManager')\nsetGeneratePomFileAndDepends('dynamicManagerMultiLoaderExt')\n"
  },
  {
    "path": "buildScripts/gradle/versions.properties",
    "content": "COMPILE_SDK_VERSION=33\nMIN_SDK_VERSION=14\nTARGET_SDK_VERSION=28\nVERSION_CODE=1\nVERSION_NAME=local\nandroid_build_tools_version=30.0.3\nandroid_support_annotations_version=28.0.0\nandroid_support_version=27.1.1\nandroidx_activity_version=1.4.0\nandroidx_appcompat_version=1.4.1\nandroidx_test_junit_version=1.1.2\nandroidx_test_version=1.3.0\nbuild_gradle_version=7.4.2\ncommons_io_android_version=2.5\ncommons_io_jvm_version=2.9.0\nespresso_version=3.3.0\njavapoet_version=1.11.1\njavassist_version=3.28.0-GA\njson_simple_version=1.1\njunit_version=4.12\nkotlin_version=1.5.31\nslf4j_version=1.7.25\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\norg.gradle.caching=true\n\nandroid.nonTransitiveRClass=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sample/README.md",
    "content": "# Sample\n\n在Shadow框架下，应用由几部分构成。\n宿主应用打包了很简单的一些接口，并在Manifest中注册了壳子代理组件，\n还打包了插件管理器（manager）的动态升级逻辑。\nmanager负责下载、安装插件，还带有一个动态的View表达Loading态。\n而\"插件\"则不光包含业务App，还包含Shadow的核心实现，即loader和runtime。\n\"插件\"中的业务App和loader、runtime是同一个版本的代码编译出的，\n因此loader可以包含一些业务逻辑，针对业务进行特殊处理。\n由于loader是多实例的，因此同一个宿主中可以有多种不同的loader实现。\nmanager在加载\"插件\"时，首先需要先加载\"插件\"中的runtime和loader，\n再通过loader的Binder（插件应该处于独立进程中避免native库冲突）操作loader进而加载业务App。\n\n在这个Sample目录下，提供了两种示例工程：\n\n## 源码依赖SDK的Sample(`projects/sample/source`)\n\n***\n要测试这个Sample请用Android Studio直接打开clone版本库的根目录。\n***\n\n* `sample-host`是宿主应用\n* `sample-manager`是插件管理器的动态实现\n* `sample-plugin/sample-loader`是loader的动态实现，业务主要在这里定义插件组件和壳子代理组件的配对关系等。\n* `sample-constant`是在前3者中共用的相同字符串常量。\n* `sample-plugin/sample-runtime`是runtime的动态实现，业务主要在这里定义壳子代理组件的实际类。\n* `sample-plugin/sample-base-lib`是业务App的一部分基础代码，是一个aar库。\n* `sample-plugin/sample-base`是一个apk模块壳子，将`sample-base-lib`打包在其中。既用于正常安装运行开发`sample-base-lib`\n  ，又用于编译`sample-base`插件。\n* `sample-plugin/sample-app`是依赖`sample-base-lib`开发的更多业务代码。它编译出的插件apk没有打包`sample-base-lib`\n  ，会在插件运行时依赖`sample-base`插件。\n\n`sample-app`和`sample-base`构成了一个多插件示例，请注意`sample-app/build.gradle`中的`dependsOn = ['sample-base']`设置。\n\n这些工程中对Shadow SDK的依赖完全是源码级的依赖，因此修改Shadow SDK的源码后可以直接运行生效。\n\n使用时可以直接在Android Studio中选择运行`sample-host`模块。\n`sample-host`在构建中会自动打包manager和\"插件\"到assets中，在运行时自动释放模拟下载过程。\n\n## 二进制Maven依赖SDK的Sample(`projects/sample/maven`)\n\n***\n要测试这个Sample请用Android Studio *分别* 打开`projects/sample/maven/host-project`\n,`projects/sample/maven/manager-project`,`projects/sample/maven/plugin-project`三个目录。\n***\n\n源码依赖SDK的Sample中对Shadow SDK的依赖配置不适用于正式业务接入。\nShadow实现了完整的Maven发布脚本，支持方便的Maven依赖。\n\n`maven`目录下的3个目录分别演示了3个工程。\n这3个工程在实际业务中大概率上是3个不同的代码库。\n因此，在这个演示中没有试图做着3个工程间的任何依赖关系，\n甚至**3个工程中依赖的Shadow版本都是独立配置的**，\n使用时请注意这一点。\n\n### 自行发布SDK到Maven仓库方法\n\n在`buildScripts/gradle/maven.gradle`文件中配置了Shadow的Maven发布脚本。\n正式使用时，请修改其中的两个GroupID变量：`coreGroupId`、`dynamicGroupId`，\n以及`setScm`方法中的两个URL到自己的版本库地址上。\n\n然后将`mavenLocal()`改为自己发布的目标Maven仓库。\n\n执行`./gradlew publish`即可将Shadow SDK发布到Maven仓库。\n\n构件的版本号可以在`build/pom`目录中查看生成的pom文件中查看。\n\n在这个Sample的3个工程的`build.gradle`文件中都有`shadow_version`定义，\n将这个定义值改为刚刚发布的版本号（生成的pom中写的版本号）。\n\n### 运行方法\n\n这个演示工程没有实现下载功能，而是假设下载的文件直接位于指定路径。\n因此运行前需要手工用adb命令将指定内容push到指定位置。\n\n编译插件，在`plugin-project`目录中运行：\n```\n./gradlew packageDebugPlugin\n\nadb push build/plugin-debug.zip /data/local/tmp\n```\n\n编译PluginManager，在`manager-project`目录中运行：\n```\n./gradlew assembleDebug\nadb push sample-manager/build/outputs/apk/debug/sample-manager-debug.apk /data/local/tmp\n```\n\n最后可以用Android Studio打开`host-project`直接运行`sample-host`模块。\n\n`plugin-project`中的`plugin-normal-apk`模块也可以直接安装运行，演示不使用Shadow时插件的运行情况。\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/README.md",
    "content": "演示如何将自定义接口动态化，使得宿主能够使用apk中的实现\n\nsample-hello-api:定义宿主api接口\nsample-hello-api-holder:将 api 动态化，宿主通过这个包提供的方法来获取apk中的实现\n\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.sample.api.hello\" />\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/HelloFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.api.hello;\n\nimport android.content.Context;\n\n/**\n * @author 林学渊\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2021/9/6\n * @description 宿主只能通过工厂模式获取 apk 中的类\n * @usage null\n */\npublic interface HelloFactory {\n    IHelloWorldImpl build(Context context);\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/IHelloWorld.java",
    "content": "package com.tencent.shadow.sample.api.hello;\n\nimport android.content.Context;\nimport android.widget.TextView;\n\n/**\n * @author 林学渊\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2021/9/6\n * @description 定义在宿主里的接口，使用插件apk中的实现\n * @usage 插件打印 hello world\n */\npublic interface IHelloWorld {\n    void sayHelloWorld(Context context, TextView textView);\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/IHelloWorldImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.api.hello;\n\nimport android.os.Bundle;\n\n/**\n * @author 林学渊\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2021/9/6\n * @description 给接口 IHelloWorld 包装一层生命周期\n * 可参考 com.tencent.shadow.sample.apk.hello.DynamicHello 中管理该生命周期\n * @usage hello.apk 里可以感知加载的过程\n */\npublic interface IHelloWorldImpl extends IHelloWorld {\n\n    void onCreate(Bundle bundle);\n\n    void onSaveInstanceState(Bundle outState);\n\n    void onDestroy();\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/README.md",
    "content": "演示如何将自定义接口动态化，使得宿主能够使用apk中的实现\n\nsample-hello-api:定义宿主api接口\nsample-hello-api-holder:将 api 动态化，宿主通过这个包提供的方法来获取apk中的实现\n\n宿主引入 apk 包，implementation project(':sample-hello-api-holder')\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    // 使用 api 而不是 compileOnly：发布 aar 时会传递依赖，而不是打包进 aar\n    api 'com.tencent.shadow.core:utils'\n    api 'com.tencent.shadow.core:common'\n    api 'com.tencent.shadow.dynamic:dynamic-apk'\n\n    api project(':sample-hello-api')\n}"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.sample.apk.hello\" />\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/DynamicHello.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.apk.hello;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.sample.api.hello.IHelloWorld;\nimport com.tencent.shadow.sample.api.hello.IHelloWorldImpl;\n\nimport java.io.File;\n\nimport static com.tencent.shadow.core.utils.Md5.md5File;\n\n\npublic final class DynamicHello implements IHelloWorld {\n\n    final private HelloWorldUpdater mUpdater;\n    private IHelloWorldImpl mHelloWorldImpl;\n    private String mCurrentImplMd5;\n    private static final Logger mLogger = LoggerFactory.getLogger(DynamicHello.class);\n\n    public DynamicHello(HelloWorldUpdater updater) {\n        if (updater.getLatest() == null) {\n            throw new IllegalArgumentException(\"构造DynamicPluginManager时传入的PluginManagerUpdater\" +\n                    \"必须已经已有本地文件，即getLatest()!=null\");\n        }\n        mUpdater = updater;\n    }\n\n    @Override\n    public void sayHelloWorld(Context context, TextView textView) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"sayHelloWorld context:\" + context);\n        }\n        updateImpl(context);\n        mHelloWorldImpl.sayHelloWorld(context, textView);\n        mUpdater.update();\n    }\n\n    public void release() {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"release\");\n        }\n        if (mHelloWorldImpl != null) {\n            mHelloWorldImpl.onDestroy();\n            mHelloWorldImpl = null;\n        }\n    }\n\n    private void updateImpl(Context context) {\n        File latestImplApk = mUpdater.getLatest();\n        String md5 = md5File(latestImplApk);\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"TextUtils.equals(mCurrentImplMd5, md5) : \" + (TextUtils.equals(mCurrentImplMd5, md5)));\n        }\n        if (!TextUtils.equals(mCurrentImplMd5, md5)) {\n            HelloImplLoader implLoader = new HelloImplLoader(context, latestImplApk);\n            IHelloWorldImpl newImpl = implLoader.load();\n            Bundle state;\n            if (mHelloWorldImpl != null) {\n                state = new Bundle();\n                mHelloWorldImpl.onSaveInstanceState(state);\n                mHelloWorldImpl.onDestroy();\n            } else {\n                state = null;\n            }\n            newImpl.onCreate(state);\n            mHelloWorldImpl = newImpl;\n            mCurrentImplMd5 = md5;\n        }\n    }\n\n    public IHelloWorld getHelloWorkdImpl() {\n        return mHelloWorldImpl;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/HelloImplLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.apk.hello;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.dynamic.apk.ApkClassLoader;\nimport com.tencent.shadow.dynamic.apk.ChangeApkContextWrapper;\nimport com.tencent.shadow.dynamic.apk.ImplLoader;\nimport com.tencent.shadow.sample.api.hello.HelloFactory;\nimport com.tencent.shadow.sample.api.hello.IHelloWorldImpl;\n\nimport java.io.File;\n\nfinal class HelloImplLoader extends ImplLoader {\n    //指定实现类在apk中的路径\n    private static final String FACTORY_CLASS_NAME = \"com.tencent.shadow.dynamic.impl.HelloFactoryImpl\";\n    private static final String[] REMOTE_PLUGIN_MANAGER_INTERFACES = new String[]\n            {\n                    \"com.tencent.shadow.core.common\",\n                    //注意将宿主自定义接口加入白名单\n                    \"com.tencent.shadow.sample.api.hello\"\n            };\n    final private Context applicationContext;\n    final private InstalledApk installedApk;\n\n    HelloImplLoader(Context context, File apk) {\n        applicationContext = context.getApplicationContext();\n        File root = new File(applicationContext.getFilesDir(), \"HelloImplLoader\");\n        File odexDir = new File(root, Long.toString(apk.lastModified(), Character.MAX_RADIX));\n        odexDir.mkdirs();\n        installedApk = new InstalledApk(apk.getAbsolutePath(), odexDir.getAbsolutePath(), null);\n    }\n\n    IHelloWorldImpl load() {\n        ApkClassLoader apkClassLoader = new ApkClassLoader(\n                installedApk,\n                getClass().getClassLoader(),\n                loadWhiteList(installedApk),\n                1\n        );\n\n        Context contextForApi = new ChangeApkContextWrapper(\n                applicationContext,\n                installedApk.apkFilePath,\n                apkClassLoader\n        );\n\n        try {\n            HelloFactory factory = apkClassLoader.getInterface(\n                    HelloFactory.class,\n                    FACTORY_CLASS_NAME\n            );\n            return factory.build(contextForApi);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    protected String[] getCustomWhiteList() {\n        return REMOTE_PLUGIN_MANAGER_INTERFACES;\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/HelloWorldUpdater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.apk.hello;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\n/**\n * apk文件升级器\n * <p>\n * 注意这个类不负责什么时候该升级 实现IHelloWorld的apk文件，\n * 它只提供需要升级时的功能，如下载和向远端查询文件是否还可用。\n */\npublic interface HelloWorldUpdater {\n    /**\n     * 更新\n     */\n    Future<File> update();\n\n    /**\n     * 获取本地最新可用的\n     *\n     * @return <code>null</code>表示本地没有可用的\n     */\n    File getLatest();\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        //region hello.apk 演示\n        applicationId 'com.tencent.shadow.sample.hello.host' // 必须保证和 host 的 applicationId 一致\n        //endregion\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    // 自定义接口在宿主内\n    // hello.apk 不必要自己打包一份，只需要通过白名单访问宿主的接口定义，所以是 compileOnly\n    compileOnly project(\":sample-hello-api\")\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.manager\" />\n\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/dynamic/impl/HelloFactoryImpl.java",
    "content": "package com.tencent.shadow.dynamic.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.sample.api.hello.HelloFactory;\nimport com.tencent.shadow.sample.api.hello.IHelloWorldImpl;\nimport com.tencent.shadow.sample.api.hello.SampleHelloWorld;\n\n/**\n * @author 林学渊\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2021/9/6\n * @description 此类包名及类名固定\n * @usage null\n */\npublic final class HelloFactoryImpl implements HelloFactory {\n    @Override\n    public IHelloWorldImpl build(Context context) {\n        return new SampleHelloWorld();\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\n/**\n * 此类包名及类名固定\n * classLoader的白名单\n * hello.apk 可以加载宿主中位于白名单内的类\n */\npublic interface WhiteList {\n    String[] sWhiteList = new String[]\n            {\n                    \"com.tencent.host.shadow\",\n                    \"com.tencent.shadow.test.lib.constant\",\n            };\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/sample/api/hello/SampleHelloWorld.java",
    "content": "package com.tencent.shadow.sample.api.hello;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\n/**\n * @author 林学渊\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2021/9/6\n * @description 实现宿主自定义接口\n * @usage null\n */\npublic class SampleHelloWorld implements IHelloWorldImpl {\n    @Override\n    public void sayHelloWorld(Context context, TextView textView) {\n        String text = \"这是apk中的实现：\" + SampleHelloWorld.class.toString();\n        if (textView == null) {\n            return;\n        }\n        textView.setText(text);\n    }\n\n    @Override\n    public void onCreate(Bundle bundle) {\n\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle outState) {\n\n    }\n\n    @Override\n    public void onDestroy() {\n\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-apk/src/main/res/layout/activity_load_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"#fcfcfc\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/black\"\n        android:layout_marginTop=\"20dp\"\n        android:textSize=\"30sp\"\n        android:text=\"这是一个位于dynamic-pluginmanager-apk中的view\" />\n\n    <ProgressBar\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n</LinearLayout>"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        //region hello.apk 演示\n        applicationId 'com.tencent.shadow.sample.hello.host'\n        //endregion\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.CustomAndroidJUnitRunner\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    //region hello.apk 演示\n    sourceSets {\n        debug {\n            assets.srcDir('build/generated/assets/sample-hello-apk/debug/')\n        }\n        release {\n            assets.srcDir('build/generated/assets/sample-hello-apk/release/')\n        }\n    }\n    //endregion\n    lintOptions {\n        checkReleaseBuilds false\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation \"commons-io:commons-io:$commons_io_android_version\"//sample-hello-host从assets中复制插件用的\n    implementation \"org.slf4j:slf4j-api:$slf4j_version\"\n\n    //region hello.apk 演示\n    implementation project(':sample-hello-api-holder')\n    //endregion\n}\n\ndef createCopyTask(projectName, buildType, name, apkName, inputFile, taskName) {\n    def outputFile = file(\"${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}\")\n    outputFile.getParentFile().mkdirs()\n    return tasks.create(\"copy${buildType.capitalize()}${name.capitalize()}Task\", Copy) {\n        group = 'build'\n        description = \"复制${name}到assets中.\"\n        from(inputFile.getParent()) {\n            include(inputFile.name)\n            rename { outputFile.name }\n        }\n        into(outputFile.getParent())\n\n    }.dependsOn(\"${projectName}:${taskName}\")\n}\n\n//region hello.apk 演示\ndef generateHelloAssets(generateAssetsTask, buildType) {\n    def moduleName = 'sample-hello-apk'\n    def pluginManagerApkFile = file(\n            \"${project(\":sample-hello-apk\").getBuildDir()}\" +\n                    \"/outputs/apk/${buildType}/\" +\n                    \"${moduleName}-${buildType}.apk\"\n    )\n    generateAssetsTask.dependsOn createCopyTask(\n            ':sample-hello-apk',\n            buildType,\n            moduleName,\n            'hello.apk',\n            pluginManagerApkFile,\n            \"assemble${buildType.capitalize()}\"\n    )\n}\n//endregion\n\ntasks.whenTaskAdded { task ->\n    if (task.name == \"generateDebugAssets\") {\n        //region hello.apk 演示\n        generateHelloAssets(task, 'debug')\n        //endregion\n    }\n    if (task.name == \"generateReleaseAssets\") {\n        //region hello.apk 演示\n        generateHelloAssets(task, 'release')\n        //endregion\n    }\n}"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.runtime.container.**{*;}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.sample.hello.host\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"Shadow 自定义接口动态化 App\"\n        android:name=\"com.tencent.shadow.sample.host.HostApplication\"\n        android:theme=\"@android:style/Theme.DeviceDefault\"\n        android:usesCleartextTraffic=\"true\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity android:name=\"com.tencent.shadow.sample.host.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/AndroidLogLoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AndroidLogLoggerFactory implements ILoggerFactory {\n\n    private static final int LOG_LEVEL_TRACE = 5;\n    private static final int LOG_LEVEL_DEBUG = 4;\n    private static final int LOG_LEVEL_INFO = 3;\n    private static final int LOG_LEVEL_WARN = 2;\n    private static final int LOG_LEVEL_ERROR = 1;\n\n    private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory();\n\n    public static ILoggerFactory getInstance() {\n        return sInstance;\n    }\n\n    final private ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();\n\n    public Logger getLogger(String name) {\n        Logger simpleLogger = loggerMap.get(name);\n        if (simpleLogger != null) {\n            return simpleLogger;\n        } else {\n            Logger newInstance = new IVLogger(name);\n            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);\n            return oldInstance == null ? newInstance : oldInstance;\n        }\n    }\n\n    class IVLogger implements Logger {\n        private String name;\n\n        IVLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        private void log(int level, String message, Throwable t) {\n            final String tag = String.valueOf(name);\n\n            switch (level) {\n                case LOG_LEVEL_TRACE:\n                case LOG_LEVEL_DEBUG:\n                    if (t == null)\n                        Log.d(tag, message);\n                    else\n                        Log.d(tag, message, t);\n                    break;\n                case LOG_LEVEL_INFO:\n                    if (t == null)\n                        Log.i(tag, message);\n                    else\n                        Log.i(tag, message, t);\n                    break;\n                case LOG_LEVEL_WARN:\n                    if (t == null)\n                        Log.w(tag, message);\n                    else\n                        Log.w(tag, message, t);\n                    break;\n                case LOG_LEVEL_ERROR:\n                    if (t == null)\n                        Log.e(tag, message);\n                    else\n                        Log.e(tag, message, t);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return true;\n        }\n\n        @Override\n        public void trace(String msg) {\n            log(LOG_LEVEL_TRACE, msg, null);\n        }\n\n        @Override\n        public void trace(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String msg, Throwable throwable) {\n            log(LOG_LEVEL_TRACE, msg, throwable);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return true;\n        }\n\n        @Override\n        public void debug(String msg) {\n            log(LOG_LEVEL_DEBUG, msg, null);\n        }\n\n        @Override\n        public void debug(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String msg, Throwable throwable) {\n            log(LOG_LEVEL_DEBUG, msg, throwable);\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return true;\n        }\n\n        @Override\n        public void info(String msg) {\n            log(LOG_LEVEL_INFO, msg, null);\n        }\n\n        @Override\n        public void info(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String msg, Throwable throwable) {\n            log(LOG_LEVEL_INFO, msg, throwable);\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return true;\n        }\n\n        @Override\n        public void warn(String msg) {\n            log(LOG_LEVEL_WARN, msg, null);\n        }\n\n        @Override\n        public void warn(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String msg, Throwable throwable) {\n            log(LOG_LEVEL_WARN, msg, throwable);\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return true;\n        }\n\n        @Override\n        public void error(String msg) {\n            log(LOG_LEVEL_ERROR, msg, null);\n        }\n\n        @Override\n        public void error(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String msg, Throwable throwable) {\n            log(LOG_LEVEL_ERROR, msg, throwable);\n        }\n    }\n}\n\nclass FormattingTuple {\n\n    static public FormattingTuple NULL = new FormattingTuple(null);\n\n    private String message;\n    private Throwable throwable;\n    private Object[] argArray;\n\n    public FormattingTuple(String message) {\n        this(message, null, null);\n    }\n\n    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {\n        this.message = message;\n        this.throwable = throwable;\n        this.argArray = argArray;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public Object[] getArgArray() {\n        return argArray;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n}\n\nfinal class MessageFormatter {\n    static final char DELIM_START = '{';\n    static final char DELIM_STOP = '}';\n    static final String DELIM_STR = \"{}\";\n    private static final char ESCAPE_CHAR = '\\\\';\n\n    /**\n     * Performs single argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi there.\".\n     * <p>\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg            The argument to be substituted in place of the formatting anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(String messagePattern, Object arg) {\n        return arrayFormat(messagePattern, new Object[]{arg});\n    }\n\n    /**\n     * Performs a two argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi Alice. My name is Bob.\".\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg1           The argument to be substituted in place of the first formatting\n     *                       anchor\n     * @param arg2           The argument to be substituted in place of the second formatting\n     *                       anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n        return arrayFormat(messagePattern, new Object[]{arg1, arg2});\n    }\n\n\n    static final Throwable getThrowableCandidate(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            return null;\n        }\n\n        final Object lastEntry = argArray[argArray.length - 1];\n        if (lastEntry instanceof Throwable) {\n            return (Throwable) lastEntry;\n        }\n        return null;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n        Throwable throwableCandidate = getThrowableCandidate(argArray);\n        Object[] args = argArray;\n        if (throwableCandidate != null) {\n            args = trimmedCopy(argArray);\n        }\n        return arrayFormat(messagePattern, args, throwableCandidate);\n    }\n\n    private static Object[] trimmedCopy(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            throw new IllegalStateException(\"non-sensical empty or null argument array\");\n        }\n        final int trimemdLen = argArray.length - 1;\n        Object[] trimmed = new Object[trimemdLen];\n        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);\n        return trimmed;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {\n\n        if (messagePattern == null) {\n            return new FormattingTuple(null, argArray, throwable);\n        }\n\n        if (argArray == null) {\n            return new FormattingTuple(messagePattern);\n        }\n\n        int i = 0;\n        int j;\n        // use string builder for better multicore performance\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n\n        int L;\n        for (L = 0; L < argArray.length; L++) {\n\n            j = messagePattern.indexOf(DELIM_STR, i);\n\n            if (j == -1) {\n                // no more variables\n                if (i == 0) { // this is a simple string\n                    return new FormattingTuple(messagePattern, argArray, throwable);\n                } else { // add the tail string which contains no variables and return\n                    // the result.\n                    sbuf.append(messagePattern, i, messagePattern.length());\n                    return new FormattingTuple(sbuf.toString(), argArray, throwable);\n                }\n            } else {\n                if (isEscapedDelimeter(messagePattern, j)) {\n                    if (!isDoubleEscaped(messagePattern, j)) {\n                        L--; // DELIM_START was escaped, thus should not be incremented\n                        sbuf.append(messagePattern, i, j - 1);\n                        sbuf.append(DELIM_START);\n                        i = j + 1;\n                    } else {\n                        // The escape character preceding the delimiter start is\n                        // itself escaped: \"abc x:\\\\{}\"\n                        // we have to consume one backward slash\n                        sbuf.append(messagePattern, i, j - 1);\n                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                        i = j + 2;\n                    }\n                } else {\n                    // normal case\n                    sbuf.append(messagePattern, i, j);\n                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                    i = j + 2;\n                }\n            }\n        }\n        // append the characters following the last {} pair.\n        sbuf.append(messagePattern, i, messagePattern.length());\n        return new FormattingTuple(sbuf.toString(), argArray, throwable);\n    }\n\n    final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {\n\n        if (delimeterStartIndex == 0) {\n            return false;\n        }\n        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);\n        if (potentialEscape == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n        if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // special treatment of array values was suggested by 'lizongbo'\n    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {\n        if (o == null) {\n            sbuf.append(\"null\");\n            return;\n        }\n        if (!o.getClass().isArray()) {\n            safeObjectAppend(sbuf, o);\n        } else {\n            // check for primitive array types because they\n            // unfortunately cannot be cast to Object[]\n            if (o instanceof boolean[]) {\n                booleanArrayAppend(sbuf, (boolean[]) o);\n            } else if (o instanceof byte[]) {\n                byteArrayAppend(sbuf, (byte[]) o);\n            } else if (o instanceof char[]) {\n                charArrayAppend(sbuf, (char[]) o);\n            } else if (o instanceof short[]) {\n                shortArrayAppend(sbuf, (short[]) o);\n            } else if (o instanceof int[]) {\n                intArrayAppend(sbuf, (int[]) o);\n            } else if (o instanceof long[]) {\n                longArrayAppend(sbuf, (long[]) o);\n            } else if (o instanceof float[]) {\n                floatArrayAppend(sbuf, (float[]) o);\n            } else if (o instanceof double[]) {\n                doubleArrayAppend(sbuf, (double[]) o);\n            } else {\n                objectArrayAppend(sbuf, (Object[]) o, seenMap);\n            }\n        }\n    }\n\n    private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n        try {\n            String oAsString = o.toString();\n            sbuf.append(oAsString);\n        } catch (Throwable t) {\n            sbuf.append(\"[FAILED toString()]\");\n        }\n\n    }\n\n    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n        sbuf.append('[');\n        if (!seenMap.containsKey(a)) {\n            seenMap.put(a, null);\n            final int len = a.length;\n            for (int i = 0; i < len; i++) {\n                deeplyAppendParameter(sbuf, a[i], seenMap);\n                if (i != len - 1)\n                    sbuf.append(\", \");\n            }\n            // allow repeats in siblings\n            seenMap.remove(a);\n        } else {\n            sbuf.append(\"...\");\n        }\n        sbuf.append(']');\n    }\n\n    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/HostApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.app.Application;\nimport android.os.Build;\nimport android.os.StrictMode;\nimport android.webkit.WebView;\n\nimport com.tencent.shadow.core.common.LoggerFactory;\n\npublic class HostApplication extends Application {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        detectNonSdkApiUsageOnAndroidP();\n        setWebViewDataDirectorySuffix();\n\n        LoggerFactory.setILoggerFactory(new AndroidLogLoggerFactory());\n        PluginHelper.getInstance().init(this);\n    }\n\n    private static void setWebViewDataDirectorySuffix() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        WebView.setDataDirectorySuffix(Application.getProcessName());\n    }\n\n    private static void detectNonSdkApiUsageOnAndroidP() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();\n        builder.detectNonSdkApiUsage();\n        StrictMode.setVmPolicy(builder.build());\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.api.hello.IHelloWorld;\nimport com.tencent.shadow.sample.host.api.HelloWorldApiHolder;\n\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setTheme(android.R.style.Theme_NoTitleBar);\n\n        LinearLayout rootView = new LinearLayout(this);\n        rootView.setOrientation(LinearLayout.VERTICAL);\n\n        rootView.addView(createTextView(\"演示自定义 api 的动态化，宿主 api 的实现在 hello.apk 中\", null));\n\n        final TextView textView = createTextView(\"等待apk实现\", null);\n        rootView.addView(createButton(\"宿主自定义接口的动态化\", new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                PluginHelper.getInstance().singlePool.execute(new Runnable() {\n                    @Override\n                    public void run() {\n                        //hello.apk 里实现了 IHelloWorld\n                        final IHelloWorld api = HelloWorldApiHolder.getHelloWorld(PluginHelper.getInstance().helloApkFile);\n                        if (api == null) {\n                            return;\n                        }\n                        runOnUiThread(new Runnable() {\n                            @Override\n                            public void run() {\n                                api.sayHelloWorld(MainActivity.this, textView);\n                            }\n                        });\n                    }\n                });\n            }\n        }));\n        rootView.addView(textView);\n\n        setContentView(rootView);\n    }\n\n    public Button createButton(String title, View.OnClickListener listener) {\n        Button button = new Button(this);\n        button.setText(title);\n        button.setOnClickListener(listener);\n        return button;\n    }\n\n    public TextView createTextView(String title, View.OnClickListener listener) {\n        TextView textView = new TextView(this);\n        textView.setText(title);\n        textView.setOnClickListener(listener);\n        return textView;\n    }\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/PluginHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.content.Context;\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class PluginHelper {\n\n    /**\n     * 动态加载的 hello.apk\n     */\n    public final static String sHelloApkName = \"hello.apk\";\n\n    public File helloApkFile;\n\n    public ExecutorService singlePool = Executors.newSingleThreadExecutor();\n\n    private Context mContext;\n\n    private static PluginHelper sInstance = new PluginHelper();\n\n    public static PluginHelper getInstance() {\n        return sInstance;\n    }\n\n    private PluginHelper() {\n    }\n\n    public void init(Context context) {\n        helloApkFile = new File(context.getFilesDir(), sHelloApkName);\n\n        mContext = context.getApplicationContext();\n\n        singlePool.execute(new Runnable() {\n            @Override\n            public void run() {\n                preparePlugin();\n            }\n        });\n\n    }\n\n    private void preparePlugin() {\n        try {\n            InputStream is = mContext.getAssets().open(sHelloApkName);\n            FileUtils.copyInputStreamToFile(is, helloApkFile);\n        } catch (IOException e) {\n            throw new RuntimeException(\"从assets中复制apk出错\", e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/api/FixedPathPmUpdater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host.api;\n\nimport com.tencent.shadow.sample.apk.hello.HelloWorldUpdater;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\npublic class FixedPathPmUpdater implements HelloWorldUpdater {\n\n    final private File apk;\n\n    FixedPathPmUpdater(File apk) {\n        this.apk = apk;\n    }\n\n    @Override\n    public Future<File> update() {\n        return null;\n    }\n\n    @Override\n    public File getLatest() {\n        return apk;\n    }\n}"
  },
  {
    "path": "projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/api/HelloWorldApiHolder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host.api;\n\nimport com.tencent.shadow.sample.api.hello.IHelloWorld;\nimport com.tencent.shadow.sample.apk.hello.DynamicHello;\n\nimport java.io.File;\n\npublic class HelloWorldApiHolder {\n\n    public static IHelloWorld getHelloWorld(File apk) {\n        final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk);\n        File tempPm = fixedPathPmUpdater.getLatest();\n        if (tempPm != null) {\n            return new DynamicHello(fixedPathPmUpdater);\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "projects/sample/maven/host-project/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext.shadow_version = '2.2.1'\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.0.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        maven {\n            name = \"GitHubPackages\"\n            url \"https://maven.pkg.github.com/tencent/shadow\"\n            //一个只读账号兼容Github Packages暂时不支持匿名下载\n            //https://github.community/t/download-from-github-package-registry-without-authentication/14407\n            credentials {\n                username = 'readonlypat'\n                password = '\\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs'\n            }\n        }\n        mavenLocal()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "projects/sample/maven/host-project/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n"
  },
  {
    "path": "projects/sample/maven/host-project/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sample/maven/host-project/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 29\n\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies {\n    implementation \"com.tencent.shadow.dynamic:host:$shadow_version\"\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.introduce_shadow_lib\">\n\n    <application>\n        <service\n            android:name=\".MainPluginProcessService\"\n            android:process=\":plugin\" />\n\n        <!--container 注册\n          注意configChanges需要全注册\n          theme需要注册成透明\n\n          这些类不打包在host中，打包在runtime中，以便减少宿主方法数增量\n          -->\n        <activity\n            android:name=\"com.tencent.shadow.sample.runtime.PluginDefaultProxyActivity\"\n            android:launchMode=\"standard\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@style/PluginContainerActivity\"\n            android:process=\":plugin\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.runtime.PluginSingleInstance1ProxyActivity\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@style/PluginContainerActivity\"\n            android:process=\":plugin\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.runtime.PluginSingleTask1ProxyActivity\"\n            android:launchMode=\"singleTask\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@style/PluginContainerActivity\"\n            android:process=\":plugin\" />\n\n        <provider\n            android:authorities=\"com.tencent.shadow.contentprovider.authority.dynamic\"\n            android:name=\"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\" />\n        <!--container 注册 end -->\n    </application>\n</manifest>\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/AndroidLoggerFactory.java",
    "content": "package com.tencent.shadow.sample.introduce_shadow_lib;\n\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AndroidLoggerFactory implements ILoggerFactory {\n\n    private static final int LOG_LEVEL_TRACE = 5;\n    private static final int LOG_LEVEL_DEBUG = 4;\n    private static final int LOG_LEVEL_INFO = 3;\n    private static final int LOG_LEVEL_WARN = 2;\n    private static final int LOG_LEVEL_ERROR = 1;\n\n    private static AndroidLoggerFactory sInstance = new AndroidLoggerFactory();\n\n    public static ILoggerFactory getInstance() {\n        return sInstance;\n    }\n\n    final private ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();\n\n    public Logger getLogger(String name) {\n        Logger simpleLogger = loggerMap.get(name);\n        if (simpleLogger != null) {\n            return simpleLogger;\n        } else {\n            Logger newInstance = new IVLogger(name);\n            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);\n            return oldInstance == null ? newInstance : oldInstance;\n        }\n    }\n\n    class IVLogger implements Logger {\n        private String name;\n\n        IVLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        private void log(int level, String message, Throwable t) {\n            final String tag = String.valueOf(name);\n\n            switch (level) {\n                case LOG_LEVEL_TRACE:\n                case LOG_LEVEL_DEBUG:\n                    if (t == null)\n                        Log.d(tag, message);\n                    else\n                        Log.d(tag, message, t);\n                    break;\n                case LOG_LEVEL_INFO:\n                    if (t == null)\n                        Log.i(tag, message);\n                    else\n                        Log.i(tag, message, t);\n                    break;\n                case LOG_LEVEL_WARN:\n                    if (t == null)\n                        Log.w(tag, message);\n                    else\n                        Log.w(tag, message, t);\n                    break;\n                case LOG_LEVEL_ERROR:\n                    if (t == null)\n                        Log.e(tag, message);\n                    else\n                        Log.e(tag, message, t);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return true;\n        }\n\n        @Override\n        public void trace(String msg) {\n            log(LOG_LEVEL_TRACE, msg, null);\n        }\n\n        @Override\n        public void trace(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String msg, Throwable throwable) {\n            log(LOG_LEVEL_TRACE, msg, throwable);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return true;\n        }\n\n        @Override\n        public void debug(String msg) {\n            log(LOG_LEVEL_DEBUG, msg, null);\n        }\n\n        @Override\n        public void debug(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String msg, Throwable throwable) {\n            log(LOG_LEVEL_DEBUG, msg, throwable);\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return true;\n        }\n\n        @Override\n        public void info(String msg) {\n            log(LOG_LEVEL_INFO, msg, null);\n        }\n\n        @Override\n        public void info(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String msg, Throwable throwable) {\n            log(LOG_LEVEL_INFO, msg, throwable);\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return true;\n        }\n\n        @Override\n        public void warn(String msg) {\n            log(LOG_LEVEL_WARN, msg, null);\n        }\n\n        @Override\n        public void warn(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String msg, Throwable throwable) {\n            log(LOG_LEVEL_WARN, msg, throwable);\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return true;\n        }\n\n        @Override\n        public void error(String msg) {\n            log(LOG_LEVEL_ERROR, msg, null);\n        }\n\n        @Override\n        public void error(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String msg, Throwable throwable) {\n            log(LOG_LEVEL_ERROR, msg, throwable);\n        }\n    }\n}\n\nclass FormattingTuple {\n\n    static public FormattingTuple NULL = new FormattingTuple(null);\n\n    private String message;\n    private Throwable throwable;\n    private Object[] argArray;\n\n    public FormattingTuple(String message) {\n        this(message, null, null);\n    }\n\n    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {\n        this.message = message;\n        this.throwable = throwable;\n        this.argArray = argArray;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public Object[] getArgArray() {\n        return argArray;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n}\n\nfinal class MessageFormatter {\n    static final char DELIM_START = '{';\n    static final char DELIM_STOP = '}';\n    static final String DELIM_STR = \"{}\";\n    private static final char ESCAPE_CHAR = '\\\\';\n\n    /**\n     * Performs single argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi there.\".\n     * <p>\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg            The argument to be substituted in place of the formatting anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(String messagePattern, Object arg) {\n        return arrayFormat(messagePattern, new Object[]{arg});\n    }\n\n    /**\n     * Performs a two argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi Alice. My name is Bob.\".\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg1           The argument to be substituted in place of the first formatting\n     *                       anchor\n     * @param arg2           The argument to be substituted in place of the second formatting\n     *                       anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n        return arrayFormat(messagePattern, new Object[]{arg1, arg2});\n    }\n\n\n    static final Throwable getThrowableCandidate(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            return null;\n        }\n\n        final Object lastEntry = argArray[argArray.length - 1];\n        if (lastEntry instanceof Throwable) {\n            return (Throwable) lastEntry;\n        }\n        return null;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n        Throwable throwableCandidate = getThrowableCandidate(argArray);\n        Object[] args = argArray;\n        if (throwableCandidate != null) {\n            args = trimmedCopy(argArray);\n        }\n        return arrayFormat(messagePattern, args, throwableCandidate);\n    }\n\n    private static Object[] trimmedCopy(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            throw new IllegalStateException(\"non-sensical empty or null argument array\");\n        }\n        final int trimemdLen = argArray.length - 1;\n        Object[] trimmed = new Object[trimemdLen];\n        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);\n        return trimmed;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {\n\n        if (messagePattern == null) {\n            return new FormattingTuple(null, argArray, throwable);\n        }\n\n        if (argArray == null) {\n            return new FormattingTuple(messagePattern);\n        }\n\n        int i = 0;\n        int j;\n        // use string builder for better multicore performance\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n\n        int L;\n        for (L = 0; L < argArray.length; L++) {\n\n            j = messagePattern.indexOf(DELIM_STR, i);\n\n            if (j == -1) {\n                // no more variables\n                if (i == 0) { // this is a simple string\n                    return new FormattingTuple(messagePattern, argArray, throwable);\n                } else { // add the tail string which contains no variables and return\n                    // the result.\n                    sbuf.append(messagePattern, i, messagePattern.length());\n                    return new FormattingTuple(sbuf.toString(), argArray, throwable);\n                }\n            } else {\n                if (isEscapedDelimeter(messagePattern, j)) {\n                    if (!isDoubleEscaped(messagePattern, j)) {\n                        L--; // DELIM_START was escaped, thus should not be incremented\n                        sbuf.append(messagePattern, i, j - 1);\n                        sbuf.append(DELIM_START);\n                        i = j + 1;\n                    } else {\n                        // The escape character preceding the delimiter start is\n                        // itself escaped: \"abc x:\\\\{}\"\n                        // we have to consume one backward slash\n                        sbuf.append(messagePattern, i, j - 1);\n                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                        i = j + 2;\n                    }\n                } else {\n                    // normal case\n                    sbuf.append(messagePattern, i, j);\n                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                    i = j + 2;\n                }\n            }\n        }\n        // append the characters following the last {} pair.\n        sbuf.append(messagePattern, i, messagePattern.length());\n        return new FormattingTuple(sbuf.toString(), argArray, throwable);\n    }\n\n    final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {\n\n        if (delimeterStartIndex == 0) {\n            return false;\n        }\n        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);\n        if (potentialEscape == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n        if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // special treatment of array values was suggested by 'lizongbo'\n    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {\n        if (o == null) {\n            sbuf.append(\"null\");\n            return;\n        }\n        if (!o.getClass().isArray()) {\n            safeObjectAppend(sbuf, o);\n        } else {\n            // check for primitive array types because they\n            // unfortunately cannot be cast to Object[]\n            if (o instanceof boolean[]) {\n                booleanArrayAppend(sbuf, (boolean[]) o);\n            } else if (o instanceof byte[]) {\n                byteArrayAppend(sbuf, (byte[]) o);\n            } else if (o instanceof char[]) {\n                charArrayAppend(sbuf, (char[]) o);\n            } else if (o instanceof short[]) {\n                shortArrayAppend(sbuf, (short[]) o);\n            } else if (o instanceof int[]) {\n                intArrayAppend(sbuf, (int[]) o);\n            } else if (o instanceof long[]) {\n                longArrayAppend(sbuf, (long[]) o);\n            } else if (o instanceof float[]) {\n                floatArrayAppend(sbuf, (float[]) o);\n            } else if (o instanceof double[]) {\n                doubleArrayAppend(sbuf, (double[]) o);\n            } else {\n                objectArrayAppend(sbuf, (Object[]) o, seenMap);\n            }\n        }\n    }\n\n    private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n        try {\n            String oAsString = o.toString();\n            sbuf.append(oAsString);\n        } catch (Throwable t) {\n            sbuf.append(\"[FAILED toString()]\");\n        }\n\n    }\n\n    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n        sbuf.append('[');\n        if (!seenMap.containsKey(a)) {\n            seenMap.put(a, null);\n            final int len = a.length;\n            for (int i = 0; i < len; i++) {\n                deeplyAppendParameter(sbuf, a[i], seenMap);\n                if (i != len - 1)\n                    sbuf.append(\", \");\n            }\n            // allow repeats in siblings\n            seenMap.remove(a);\n        } else {\n            sbuf.append(\"...\");\n        }\n        sbuf.append(']');\n    }\n\n    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/FixedPathPmUpdater.java",
    "content": "package com.tencent.shadow.sample.introduce_shadow_lib;\n\nimport com.tencent.shadow.dynamic.host.PluginManagerUpdater;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\n/**\n * 这个Updater没有任何升级能力。直接将指定路径作为其升级结果。\n */\npublic class FixedPathPmUpdater implements PluginManagerUpdater {\n\n    final private File apk;\n\n    FixedPathPmUpdater(File apk) {\n        this.apk = apk;\n    }\n\n\n    @Override\n    public boolean wasUpdating() {\n        return false;\n    }\n\n    @Override\n    public Future<File> update() {\n        return null;\n    }\n\n    @Override\n    public File getLatest() {\n        return apk;\n    }\n\n    @Override\n    public Future<Boolean> isAvailable(final File file) {\n        return null;\n    }\n}"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/InitApplication.java",
    "content": "package com.tencent.shadow.sample.introduce_shadow_lib;\n\nimport android.app.ActivityManager;\nimport android.app.Application;\nimport android.content.Context;\n\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.dynamic.host.DynamicPluginManager;\nimport com.tencent.shadow.dynamic.host.DynamicRuntime;\nimport com.tencent.shadow.dynamic.host.PluginManager;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\nimport static android.os.Process.myPid;\n\npublic class InitApplication {\n\n    /**\n     * 这个PluginManager对象在Manager升级前后是不变的。它内部持有具体实现，升级时更换具体实现。\n     */\n    private static PluginManager sPluginManager;\n\n    public static PluginManager getPluginManager() {\n        return sPluginManager;\n    }\n\n    public static void onApplicationCreate(Application application) {\n        //Log接口Manager也需要使用，所以主进程也初始化。\n        LoggerFactory.setILoggerFactory(new AndroidLoggerFactory());\n\n        if (isProcess(application, \":plugin\")) {\n            //在全动态架构中，Activity组件没有打包在宿主而是位于被动态加载的runtime，\n            //为了防止插件crash后，系统自动恢复crash前的Activity组件，此时由于没有加载runtime而发生classNotFound异常，导致二次crash\n            //因此这里恢复加载上一次的runtime\n            DynamicRuntime.recoveryRuntime(application);\n        }\n\n        if (isProcess(application, application.getPackageName())) {\n            FixedPathPmUpdater fixedPathPmUpdater\n                    = new FixedPathPmUpdater(new File(\"/data/local/tmp/sample-manager-debug.apk\"));\n            boolean needWaitingUpdate\n                    = fixedPathPmUpdater.wasUpdating()//之前正在更新中，暗示更新出错了，应该放弃之前的缓存\n                    || fixedPathPmUpdater.getLatest() == null;//没有本地缓存\n            Future<File> update = fixedPathPmUpdater.update();\n            if (needWaitingUpdate) {\n                try {\n                    update.get();//这里是阻塞的，需要业务自行保证更新Manager足够快。\n                } catch (Exception e) {\n                    throw new RuntimeException(\"Sample程序不容错\", e);\n                }\n            }\n            sPluginManager = new DynamicPluginManager(fixedPathPmUpdater);\n        }\n    }\n\n    private static boolean isProcess(Context context, String processName) {\n        String currentProcName = \"\";\n        ActivityManager manager =\n                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {\n            if (processInfo.pid == myPid()) {\n                currentProcName = processInfo.processName;\n                break;\n            }\n        }\n\n        return currentProcName.endsWith(processName);\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/MainPluginProcessService.java",
    "content": "package com.tencent.shadow.sample.introduce_shadow_lib;\n\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\n\n/**\n * 一个PluginProcessService（简称PPS）代表一个插件进程。插件进程由PPS启动触发启动。\n * 新建PPS子类允许一个宿主中有多个互不影响的插件进程。\n */\npublic class MainPluginProcessService extends PluginProcessService {\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/introduce-shadow-lib/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"PluginContainerActivity\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n    </style>\n</resources>"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 29\n    defaultConfig {\n        applicationId \"com.tencent.shadow.sample.host\"\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation project(':introduce-shadow-lib')\n\n    //如果introduce-shadow-lib发布到Maven，在pom中写明此依赖，宿主就不用写这个依赖了。\n    implementation \"com.tencent.shadow.dynamic:host:$shadow_version\"\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.runtime.**{*;}\n-keep class com.tencent.shadow.dynamic.host.**{*;}"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.host\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"Shadow Sample Host\"\n        android:name=\".MyApplication\"\n        android:theme=\"@android:style/Theme.DeviceDefault\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java",
    "content": "package com.tencent.shadow.sample.host;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.dynamic.host.PluginManager;\nimport com.tencent.shadow.sample.introduce_shadow_lib.InitApplication;\n\npublic class MainActivity extends Activity {\n\n    public static final int FROM_ID_START_ACTIVITY = 1001;\n    public static final int FROM_ID_CALL_SERVICE = 1002;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        final LinearLayout linearLayout = new LinearLayout(this);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n\n        TextView textView = new TextView(this);\n        textView.setText(\"宿主App\");\n\n        Button button = new Button(this);\n        button.setText(\"启动插件\");\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(final View v) {\n                v.setEnabled(false);//防止点击重入\n\n                PluginManager pluginManager = InitApplication.getPluginManager();\n                pluginManager.enter(MainActivity.this, FROM_ID_START_ACTIVITY, new Bundle(), new EnterCallback() {\n                    @Override\n                    public void onShowLoadingView(View view) {\n                        MainActivity.this.setContentView(view);//显示Manager传来的Loading页面\n                    }\n\n                    @Override\n                    public void onCloseLoadingView() {\n                        MainActivity.this.setContentView(linearLayout);\n                    }\n\n                    @Override\n                    public void onEnterComplete() {\n                        v.setEnabled(true);\n                    }\n                });\n            }\n        });\n\n        linearLayout.addView(textView);\n        linearLayout.addView(button);\n\n        Button callServiceButton = new Button(this);\n        callServiceButton.setText(\"调用插件Service，结果打印到Log\");\n        callServiceButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                v.setEnabled(false);//防止点击重入\n\n                PluginManager pluginManager = InitApplication.getPluginManager();\n                pluginManager.enter(MainActivity.this, FROM_ID_CALL_SERVICE, null, null);\n            }\n        });\n\n        linearLayout.addView(callServiceButton);\n\n        setContentView(linearLayout);\n    }\n}"
  },
  {
    "path": "projects/sample/maven/host-project/sample-host/src/main/java/com/tencent/shadow/sample/host/MyApplication.java",
    "content": "package com.tencent.shadow.sample.host;\n\nimport android.app.Application;\n\nimport com.tencent.shadow.sample.introduce_shadow_lib.InitApplication;\n\npublic class MyApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n\n        InitApplication.onApplicationCreate(this);\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/host-project/settings.gradle",
    "content": "include ':sample-host', ':introduce-shadow-lib'\n"
  },
  {
    "path": "projects/sample/maven/manager-project/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "projects/sample/maven/manager-project/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext.shadow_version = '2.2.1'\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.0.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        maven {\n            name = \"GitHubPackages\"\n            url \"https://maven.pkg.github.com/tencent/shadow\"\n            //一个只读账号兼容Github Packages暂时不支持匿名下载\n            //https://github.community/t/download-from-github-package-registry-without-authentication/14407\n            credentials {\n                username = 'readonlypat'\n                password = '\\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs'\n            }\n        }\n        mavenLocal()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed May 08 11:09:16 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip\n"
  },
  {
    "path": "projects/sample/maven/manager-project/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n"
  },
  {
    "path": "projects/sample/maven/manager-project/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sample/maven/manager-project/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 29\n    defaultConfig {\n        applicationId \"com.tencent.shadow.sample.manager\"\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation \"com.tencent.shadow.dynamic:manager:$shadow_version\"\n    compileOnly \"com.tencent.shadow.core:common:$shadow_version\"\n    compileOnly \"com.tencent.shadow.dynamic:host:$shadow_version\"\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/proguard-rules.pro",
    "content": "-keep class com.tencent.shadow.dynamic.impl.**{*;}\n\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.manager\" />\n\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/aidl/com/tencent/shadow/sample/plugin/IMyAidlInterface.aidl",
    "content": "// IMyAidlInterface.aidl\npackage com.tencent.shadow.sample.plugin;\n\n// Declare any non-default types here with import statements\n\ninterface IMyAidlInterface {\n    /**\n     * Demonstrates some basic types that you can use as parameters\n     * and return values in AIDL.\n     */\n    String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,\n            double aDouble, String aString);\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/ManagerFactoryImpl.java",
    "content": "package com.tencent.shadow.dynamic.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.dynamic.host.ManagerFactory;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.sample.manager.SamplePluginManager;\n\n/**\n * 此类包名及类名固定\n */\npublic final class ManagerFactoryImpl implements ManagerFactory {\n    @Override\n    public PluginManagerImpl buildManager(Context context) {\n        return new SamplePluginManager(context);\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java",
    "content": "package com.tencent.shadow.dynamic.impl;\n\n/**\n * 此类包名及类名固定\n * classLoader的白名单\n * PluginManager可以加载宿主中位于白名单内的类\n */\npublic interface WhiteList {\n    String[] sWhiteList = new String[]\n            {\n            };\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/Constant.java",
    "content": "package com.tencent.shadow.sample.manager;\n\nfinal public class Constant {\n    public static final String KEY_PLUGIN_ZIP_PATH = \"pluginZipPath\";\n    public static final String KEY_ACTIVITY_CLASSNAME = \"KEY_ACTIVITY_CLASSNAME\";\n    public static final String KEY_EXTRAS = \"KEY_EXTRAS\";\n    public static final String KEY_PLUGIN_PART_KEY = \"KEY_PLUGIN_PART_KEY\";\n    public static final int FROM_ID_START_ACTIVITY = 1001;\n    public static final int FROM_ID_CALL_SERVICE = 1002;\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/FastPluginManager.java",
    "content": "package com.tencent.shadow.sample.manager;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.core.manager.installplugin.InstalledType;\nimport com.tencent.shadow.core.manager.installplugin.PluginConfig;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.manager.PluginManagerThatUseDynamicLoader;\n\nimport org.json.JSONException;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\npublic abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(FastPluginManager.class);\n\n    private ExecutorService mFixedPool = Executors.newFixedThreadPool(4);\n\n    public FastPluginManager(Context context) {\n        super(context);\n    }\n\n\n    public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {\n        final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash);\n        final String uuid = pluginConfig.UUID;\n        List<Future> futures = new LinkedList<>();\n        if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) {\n            Future odexRuntime = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME,\n                            pluginConfig.runTime.file);\n                    return null;\n                }\n            });\n            futures.add(odexRuntime);\n            Future odexLoader = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER,\n                            pluginConfig.pluginLoader.file);\n                    return null;\n                }\n            });\n            futures.add(odexLoader);\n        }\n        for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : pluginConfig.plugins.entrySet()) {\n            final String partKey = plugin.getKey();\n            final File apkFile = plugin.getValue().file;\n            Future extractSo = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    extractSo(uuid, partKey, apkFile);\n                    return null;\n                }\n            });\n            futures.add(extractSo);\n            if (odex) {\n                Future odexPlugin = mFixedPool.submit(new Callable() {\n                    @Override\n                    public Object call() throws Exception {\n                        oDexPlugin(uuid, partKey, apkFile);\n                        return null;\n                    }\n                });\n                futures.add(odexPlugin);\n            }\n        }\n\n        for (Future future : futures) {\n            future.get();\n        }\n        onInstallCompleted(pluginConfig);\n\n        return getInstalledPlugins(1).get(0);\n    }\n\n\n    public void startPluginActivity(Context context, InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {\n        Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent);\n        if (!(context instanceof Activity)) {\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n        context.startActivity(intent);\n    }\n\n    public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {\n        loadPlugin(installedPlugin.UUID, partKey);\n        return mPluginLoader.convertActivityIntent(pluginIntent);\n    }\n\n    private void loadPluginLoaderAndRuntime(String uuid) throws RemoteException, TimeoutException, FailedException {\n        if (mPpsController == null) {\n            bindPluginProcessService(getPluginProcessServiceName());\n            waitServiceConnected(10, TimeUnit.SECONDS);\n        }\n        loadRunTime(uuid);\n        loadPluginLoader(uuid);\n    }\n\n    protected void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {\n        loadPluginLoaderAndRuntime(uuid);\n        Map map = mPluginLoader.getLoadedPlugin();\n        if (!map.containsKey(partKey)) {\n            mPluginLoader.loadPlugin(partKey);\n        }\n        Boolean isCall = (Boolean) map.get(partKey);\n        if (isCall == null || !isCall) {\n            mPluginLoader.callApplicationOnCreate(partKey);\n        }\n    }\n\n\n    protected abstract String getPluginProcessServiceName();\n\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/SamplePluginManager.java",
    "content": "package com.tencent.shadow.sample.manager;\n\nimport android.app.Service;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.*;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection;\nimport com.tencent.shadow.sample.plugin.IMyAidlInterface;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n\npublic class SamplePluginManager extends FastPluginManager {\n\n    private ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n    private Context mCurrentContext;\n\n    public SamplePluginManager(Context context) {\n        super(context);\n        mCurrentContext = context;\n    }\n\n    /**\n     * @return PluginManager实现的别名，用于区分不同PluginManager实现的数据存储路径\n     */\n    @Override\n    protected String getName() {\n        return \"sample-manager\";\n    }\n\n    /**\n     * @return 宿主中注册的PluginProcessService实现的类名\n     */\n    @Override\n    protected String getPluginProcessServiceName() {\n        return \"com.tencent.shadow.sample.introduce_shadow_lib.MainPluginProcessService\";\n    }\n\n    @Override\n    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {\n        if (fromId == Constant.FROM_ID_START_ACTIVITY) {\n            bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, \"/data/local/tmp/plugin-debug.zip\");\n            bundle.putString(Constant.KEY_PLUGIN_PART_KEY, \"sample-plugin\");\n            bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, \"com.tencent.shadow.sample.plugin.MainActivity\");\n            onStartActivity(context, bundle, callback);\n        } else if (fromId == Constant.FROM_ID_CALL_SERVICE) {\n            callPluginService(context);\n        } else {\n            throw new IllegalArgumentException(\"不认识的fromId==\" + fromId);\n        }\n    }\n\n    private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) {\n        final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH);\n        final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY);\n        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);\n        if (className == null) {\n            throw new NullPointerException(\"className == null\");\n        }\n        final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);\n\n        if (callback != null) {\n            final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);\n            callback.onShowLoadingView(view);\n        }\n\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    InstalledPlugin installedPlugin\n                            = installPlugin(pluginZipPath, null, true);//这个调用是阻塞的\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(\n                            context.getPackageName(),\n                            className\n                    );\n                    if (extras != null) {\n                        pluginIntent.replaceExtras(extras);\n                    }\n\n                    startPluginActivity(context, installedPlugin, partKey, pluginIntent);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n                if (callback != null) {\n                    Handler uiHandler = new Handler(Looper.getMainLooper());\n                    uiHandler.post(new Runnable() {\n                        @Override\n                        public void run() {\n                            callback.onCloseLoadingView();\n                            callback.onEnterComplete();\n                        }\n                    });\n                }\n            }\n        });\n    }\n\n    private void callPluginService(final Context context) {\n        final String pluginZipPath = \"/data/local/tmp/plugin-debug.zip\";\n        final String partKey = \"sample-plugin\";\n        final String className = \"com.tencent.shadow.sample.plugin.MyService\";\n\n        Intent pluginIntent = new Intent();\n        pluginIntent.setClassName(context.getPackageName(), className);\n\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    InstalledPlugin installedPlugin\n                            = installPlugin(pluginZipPath, null, true);//这个调用是阻塞的\n\n                    loadPlugin(installedPlugin.UUID, partKey);\n\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(context.getPackageName(), className);\n\n                    boolean callSuccess = mPluginLoader.bindPluginService(pluginIntent, new PluginServiceConnection() {\n                        @Override\n                        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {\n                            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);\n                            try {\n                                String s = iMyAidlInterface.basicTypes(1, 2, true, 4.0f, 5.0, \"6\");\n                                Log.i(\"SamplePluginManager\", \"iMyAidlInterface.basicTypes : \" + s);\n                            } catch (RemoteException e) {\n                                throw new RuntimeException(e);\n                            }\n                        }\n\n                        @Override\n                        public void onServiceDisconnected(ComponentName componentName) {\n                            throw new RuntimeException(\"onServiceDisconnected\");\n                        }\n                    }, Service.BIND_AUTO_CREATE);\n\n                    if (!callSuccess) {\n                        throw new RuntimeException(\"bind service失败 className==\" + className);\n                    }\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/manager-project/sample-manager/src/main/res/layout/activity_load_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"#fcfcfc\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/black\"\n        android:layout_marginTop=\"20dp\"\n        android:textSize=\"30sp\"\n        android:text=\"正在下载加载插件\" />\n\n    <ProgressBar\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n</LinearLayout>"
  },
  {
    "path": "projects/sample/maven/manager-project/settings.gradle",
    "content": "include ':sample-manager'\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext.shadow_version = '2.2.1'\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.0.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        maven {\n            name = \"GitHubPackages\"\n            url \"https://maven.pkg.github.com/tencent/shadow\"\n            //一个只读账号兼容Github Packages暂时不支持匿名下载\n            //https://github.community/t/download-from-github-package-registry-without-authentication/14407\n            credentials {\n                username = 'readonlypat'\n                password = '\\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs'\n            }\n        }\n        mavenLocal()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed May 08 11:09:16 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion 30\n    defaultConfig {\n        applicationId \"com.tencent.shadow.sample.plugin\"\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId \"com.tencent.shadow.sample.host\"\n        }\n    }\n}\n\ndependencies {\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly \"com.tencent.shadow.core:runtime:$shadow_version\"\n}\n\n//这段buildscript配置的dependencies是为了apply plugin: 'com.tencent.shadow.plugin'能找到实现\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        maven {\n            name = \"GitHubPackages\"\n            url \"https://maven.pkg.github.com/tencent/shadow\"\n            //一个只读账号兼容Github Packages暂时不支持匿名下载\n            //https://github.community/t/download-from-github-package-registry-without-authentication/14407\n            credentials {\n                username = 'readonlypat'\n                password = '\\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs'\n            }\n        }\n        mavenLocal()\n    }\n\n    dependencies {\n        classpath \"com.tencent.shadow.core:gradle-plugin:$shadow_version\"\n    }\n}\n\nshadow {\n    packagePlugin {\n        pluginTypes {\n            debug {\n                loaderApkConfig = new Tuple2('sample-loader-debug.apk', ':sample-loader:assembleDebug')\n                runtimeApkConfig = new Tuple2('sample-runtime-debug.apk', ':sample-runtime:assembleDebug')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'sample-plugin'//businessName相同的插件，context获取的Dir是相同的。businessName留空，表示和宿主相同业务，直接使用宿主的Dir。\n                        partKey = 'sample-plugin'\n                        buildTask = 'assemblePluginDebug'\n                        apkName = 'plugin-app-plugin-debug.apk'\n                        apkPath = 'plugin-app/build/outputs/apk/plugin/debug/plugin-app-plugin-debug.apk'\n                    }\n                }\n            }\n\n            release {\n                loaderApkConfig = new Tuple2('sample-loader-release.apk', ':sample-loader:assembleRelease')\n                runtimeApkConfig = new Tuple2('sample-runtime-release.apk', ':sample-runtime:assembleRelease')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'demo'\n                        partKey = 'sample-plugin'\n                        buildTask = 'assemblePluginRelease'\n                        apkName = 'plugin-app-plugin-release.apk'\n                        apkPath = 'plugin-app/build/outputs/apk/plugin/release/plugin-app-plugin-release.apk'\n                    }\n                }\n            }\n        }\n\n        loaderApkProjectPath = 'sample-loader'\n\n        runtimeApkProjectPath = 'sample-runtime'\n\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.plugin\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <service android:name=\".MyService\"></service>\n\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/aidl/com/tencent/shadow/sample/plugin/IMyAidlInterface.aidl",
    "content": "// IMyAidlInterface.aidl\npackage com.tencent.shadow.sample.plugin;\n\n// Declare any non-default types here with import statements\n\ninterface IMyAidlInterface {\n    /**\n     * Demonstrates some basic types that you can use as parameters\n     * and return values in AIDL.\n     */\n    String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,\n            double aDouble, String aString);\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/java/com/tencent/shadow/sample/plugin/MainActivity.java",
    "content": "package com.tencent.shadow.sample.plugin;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\npublic class MainActivity extends Activity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n    }\n}"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/java/com/tencent/shadow/sample/plugin/MyService.java",
    "content": "package com.tencent.shadow.sample.plugin;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\npublic class MyService extends Service {\n    public MyService() {\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return new IMyAidlInterface.Stub() {\n            @Override\n            public String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {\n                return Integer.toString(anInt) + aLong + aBoolean + aFloat + aDouble + aString;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"这是插件\" />\n\n</FrameLayout>"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#008577</color>\n    <color name=\"colorPrimaryDark\">#00574B</color>\n    <color name=\"colorAccent\">#D81B60</color>\n</resources>\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Shadow Sample Plugin</string>\n</resources>\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/plugin-app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.NoTitleBar\"></style>\n\n</resources>\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 29\n    defaultConfig {\n        applicationId \"com.tencent.shadow.sample.loader\"//applicationId不重要\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation \"com.tencent.shadow.dynamic:loader-impl:$shadow_version\"\n\n    compileOnly \"com.tencent.shadow.core:activity-container:$shadow_version\"\n    compileOnly \"com.tencent.shadow.core:common:$shadow_version\"\n    //下面这行依赖是为了防止在proguard的时候找不到LoaderFactory接口\n    compileOnly \"com.tencent.shadow.dynamic:host:$shadow_version\"\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n\n#kotlin一般性配置 START\n-dontwarn kotlin.**\n-keepclassmembers class **$WhenMappings {\n    <fields>;\n}\n-keepclassmembers class kotlin.Metadata {\n    public <methods>;\n}\n#kotlin一般性配置 END\n\n#kotlin优化性能 START\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);\n}\n#kotlin优化性能 END\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.loader.**{*;}\n-keep class com.tencent.shadow.core.runtime.**{*;}\n\n-dontwarn  com.tencent.shadow.dynamic.host.**\n-dontwarn  com.tencent.shadow.dynamic.impl.**\n-dontwarn  com.tencent.shadow.dynamic.loader.**\n-dontwarn  com.tencent.shadow.core.common.**\n-dontwarn  com.tencent.shadow.core.loader.**\n-dontwarn module-info\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.loader\" />\n\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/dynamic/loader/impl/CoreLoaderFactoryImpl.java",
    "content": "package com.tencent.shadow.dynamic.loader.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.sample.loader.SamplePluginLoader;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * 这个类的包名类名是固定的。\n * <p>\n * 见com.tencent.shadow.dynamic.loader.impl.DynamicPluginLoader#CORE_LOADER_FACTORY_IMPL_NAME\n */\npublic class CoreLoaderFactoryImpl implements CoreLoaderFactory {\n\n    @NotNull\n    @Override\n    public ShadowPluginLoader build(@NotNull Context context) {\n        return new SamplePluginLoader(context);\n    }\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/sample/loader/SampleComponentManager.java",
    "content": "package com.tencent.shadow.sample.loader;\n\nimport android.content.ComponentName;\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SampleComponentManager extends ComponentManager {\n\n    /**\n     * sample-runtime 模块中定义的壳子Activity，需要在宿主AndroidManifest.xml注册\n     */\n    private static final String DEFAULT_ACTIVITY = \"com.tencent.shadow.sample.runtime.PluginDefaultProxyActivity\";\n    private static final String SINGLE_INSTANCE_ACTIVITY = \"com.tencent.shadow.sample.runtime.PluginSingleInstance1ProxyActivity\";\n    private static final String SINGLE_TASK_ACTIVITY = \"com.tencent.shadow.sample.runtime.PluginSingleTask1ProxyActivity\";\n\n    private Context context;\n\n    public SampleComponentManager(Context context) {\n        this.context = context;\n    }\n\n\n    /**\n     * 配置插件Activity 到 壳子Activity的对应关系\n     *\n     * @param pluginActivity 插件Activity\n     * @return 壳子Activity\n     */\n    @Override\n    public ComponentName onBindContainerActivity(ComponentName pluginActivity) {\n        switch (pluginActivity.getClassName()) {\n            /**\n             * 这里配置对应的对应关系\n             */\n        }\n        return new ComponentName(context, DEFAULT_ACTIVITY);\n    }\n\n    /**\n     * 配置对应宿主中预注册的壳子contentProvider的信息\n     */\n    @Override\n    public ContainerProviderInfo onBindContainerContentProvider(ComponentName pluginContentProvider) {\n        return new ContainerProviderInfo(\n                \"com.tencent.shadow.runtime.container.PluginContainerContentProvider\",\n                \"com.tencent.shadow.contentprovider.authority.dynamic\");\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/sample/loader/SamplePluginLoader.java",
    "content": "package com.tencent.shadow.sample.loader;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\nimport com.tencent.shadow.sample.loader.SampleComponentManager;\n\n/**\n * 这里的类名和包名需要固定\n * com.tencent.shadow.sdk.pluginloader.PluginLoaderImpl\n */\npublic class SamplePluginLoader extends ShadowPluginLoader {\n\n    private final static String TAG = \"shadow\";\n\n    private ComponentManager componentManager;\n\n    public SamplePluginLoader(Context hostAppContext) {\n        super(hostAppContext);\n        componentManager = new SampleComponentManager(hostAppContext);\n    }\n\n    @Override\n    public ComponentManager getComponentManager() {\n        return componentManager;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 29\n    defaultConfig {\n        applicationId \"com.tencent.shadow.sample.runtime\"//applicationId不重要\n        minSdkVersion 16\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation \"com.tencent.shadow.core:activity-container:$shadow_version\"\n}\n\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.core.runtime.**{*;}\n#需要keep在宿主AndroidManifest.xml注册的壳子activity\n-keep class com.tencent.shadow.sample.runtime.**{*;}\n\n#GeneratedPluginContainerActivity包含新版本API的接口，可能在业务编译时使用的低版本compileSDK中找不到\n-dontwarn com.tencent.shadow.core.runtime.container.GeneratedPluginContainerActivity\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.runtime\" />\n\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginDefaultProxyActivity.java",
    "content": "package com.tencent.shadow.sample.runtime;\n\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class PluginDefaultProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginSingleInstance1ProxyActivity.java",
    "content": "package com.tencent.shadow.sample.runtime;\n\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class PluginSingleInstance1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginSingleTask1ProxyActivity.java",
    "content": "package com.tencent.shadow.sample.runtime;\n\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class PluginSingleTask1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/sample/maven/plugin-project/settings.gradle",
    "content": "include ':plugin-app'\ninclude ':sample-runtime'\ninclude ':sample-loader'\n"
  },
  {
    "path": "projects/sample/source/sample-constant/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-constant/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sample/source/sample-constant/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/source/sample-constant/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.sample.constant\" />\n"
  },
  {
    "path": "projects/sample/source/sample-constant/src/main/java/com/tencent/shadow/sample/constant/Constant.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.constant;\n\nfinal public class Constant {\n    public static final String KEY_PLUGIN_ZIP_PATH = \"pluginZipPath\";\n    public static final String KEY_ACTIVITY_CLASSNAME = \"KEY_ACTIVITY_CLASSNAME\";\n    public static final String KEY_EXTRAS = \"KEY_EXTRAS\";\n    public static final String KEY_PLUGIN_PART_KEY = \"KEY_PLUGIN_PART_KEY\";\n    public static final String PART_KEY_PLUGIN_MAIN_APP = \"sample-plugin-app\";\n    public static final String PART_KEY_PLUGIN_ANOTHER_APP = \"sample-plugin-app2\";\n    public static final String PART_KEY_PLUGIN_BASE = \"sample-base\";\n\n    public static final int FROM_ID_NOOP = 1000;\n    public static final int FROM_ID_START_ACTIVITY = 1002;\n    public static final int FROM_ID_CLOSE = 1003;\n    public static final int FROM_ID_LOAD_VIEW_TO_HOST = 1004;\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.CustomAndroidJUnitRunner\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    sourceSets {\n        debug {\n            assets.srcDir('build/generated/assets/sample-manager/debug/')\n            assets.srcDir('build/generated/assets/plugin-zip/debug/')\n        }\n        release {\n            assets.srcDir('build/generated/assets/sample-manager/release/')\n            assets.srcDir('build/generated/assets/plugin-zip/release/')\n        }\n    }\n    lintOptions {\n        checkReleaseBuilds false\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation \"commons-io:commons-io:$commons_io_android_version\"//sample-host从assets中复制插件用的\n    implementation \"org.slf4j:slf4j-api:$slf4j_version\"\n\n    implementation 'com.tencent.shadow.core:common'\n    implementation 'com.tencent.shadow.dynamic:dynamic-host'\n    implementation project(':sample-constant')\n    implementation project(':sample-host-lib')\n}\n\ndef createCopyTask(projectName, buildType, name, apkName, inputFile, taskName) {\n    def outputFile = file(\"${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}\")\n    outputFile.getParentFile().mkdirs()\n    return tasks.create(\"copy${buildType.capitalize()}${name.capitalize()}Task\", Copy) {\n        group = 'build'\n        description = \"复制${name}到assets中.\"\n        from(inputFile.getParent()) {\n            include(inputFile.name)\n            rename { outputFile.name }\n        }\n        into(outputFile.getParent())\n\n    }.dependsOn(\"${projectName}:${taskName}\")\n}\n\ndef generateAssets(generateAssetsTask, buildType) {\n\n    def moduleName = 'sample-manager'\n    def pluginManagerApkFile = file(\n            \"${project(\":sample-manager\").getBuildDir()}\" +\n                    \"/outputs/apk/${buildType}/\" +\n                    \"${moduleName}-${buildType}.apk\"\n    )\n    generateAssetsTask.dependsOn createCopyTask(\n            ':sample-manager',\n            buildType,\n            moduleName,\n            'pluginmanager.apk',\n            pluginManagerApkFile,\n            \"assemble${buildType.capitalize()}\"\n    )\n\n    def pluginZip = file(\"${getRootProject().getBuildDir()}/plugin-${buildType}.zip\")\n    generateAssetsTask.dependsOn createCopyTask(\n            ':sample-app',\n            buildType,\n            'plugin-zip',\n            \"plugin-${buildType}.zip\",\n            pluginZip,\n            \"package${buildType.capitalize()}Plugin\"\n    )\n\n\n}\n\ntasks.whenTaskAdded { task ->\n    if (task.name == \"generateDebugAssets\") {\n        generateAssets(task, 'debug')\n    }\n    if (task.name == \"generateReleaseAssets\") {\n        generateAssets(task, 'release')\n    }\n}"
  },
  {
    "path": "projects/sample/source/sample-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.runtime.container.**{*;}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.sample.host\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"Shadow Dyanamic测试宿主App\"\n        android:name=\"com.tencent.shadow.sample.host.HostApplication\"\n        android:theme=\"@android:style/Theme.DeviceDefault\"\n        android:usesCleartextTraffic=\"true\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity android:name=\"com.tencent.shadow.sample.host.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".plugin_view.HostAddPluginViewActivity\"\n            android:process=\":plugin\" />\n\n        <provider\n            android:authorities=\"${applicationId}.contentprovider.authority.dynamic\"\n            android:name=\"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\"\n            android:grantUriPermissions=\"true\"\n            android:process=\":plugin\" />\n\n        <service\n            android:name=\"com.tencent.shadow.sample.host.PluginProcessPPS\"\n            android:process=\":plugin\" />\n        <service\n            android:name=\"com.tencent.shadow.sample.host.Plugin2ProcessPPS\"\n            android:process=\":plugin2\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.host.PluginLoadActivity\"\n            android:launchMode=\"standard\"\n            android:screenOrientation=\"portrait\" />\n\n        <!--dynamic activity注册\n          注意configChanges需要全注册\n          theme需要注册成透明\n          -->\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.runtime.PluginDefaultProxyActivity\"\n            android:launchMode=\"standard\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\"\n            android:multiprocess=\"true\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleInstance1ProxyActivity\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\"\n            android:multiprocess=\"true\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleTask1ProxyActivity\"\n            android:launchMode=\"singleTask\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\"\n            android:multiprocess=\"true\"\n\n            />\n        <!--dynamic activity注册 end -->\n        <receiver android:name=\".plugin_view.MainProcessManagerReceiver\">\n            <intent-filter>\n                <action android:name=\"sample_host.manager.startPluginService\" />\n            </intent-filter>\n        </receiver>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/AndroidLogLoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AndroidLogLoggerFactory implements ILoggerFactory {\n\n    private static final int LOG_LEVEL_TRACE = 5;\n    private static final int LOG_LEVEL_DEBUG = 4;\n    private static final int LOG_LEVEL_INFO = 3;\n    private static final int LOG_LEVEL_WARN = 2;\n    private static final int LOG_LEVEL_ERROR = 1;\n\n    private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory();\n\n    public static ILoggerFactory getInstance() {\n        return sInstance;\n    }\n\n    final private ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();\n\n    public Logger getLogger(String name) {\n        Logger simpleLogger = loggerMap.get(name);\n        if (simpleLogger != null) {\n            return simpleLogger;\n        } else {\n            Logger newInstance = new IVLogger(name);\n            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);\n            return oldInstance == null ? newInstance : oldInstance;\n        }\n    }\n\n    class IVLogger implements Logger {\n        private String name;\n\n        IVLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        private void log(int level, String message, Throwable t) {\n            final String tag = String.valueOf(name);\n\n            switch (level) {\n                case LOG_LEVEL_TRACE:\n                case LOG_LEVEL_DEBUG:\n                    if (t == null)\n                        Log.d(tag, message);\n                    else\n                        Log.d(tag, message, t);\n                    break;\n                case LOG_LEVEL_INFO:\n                    if (t == null)\n                        Log.i(tag, message);\n                    else\n                        Log.i(tag, message, t);\n                    break;\n                case LOG_LEVEL_WARN:\n                    if (t == null)\n                        Log.w(tag, message);\n                    else\n                        Log.w(tag, message, t);\n                    break;\n                case LOG_LEVEL_ERROR:\n                    if (t == null)\n                        Log.e(tag, message);\n                    else\n                        Log.e(tag, message, t);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return true;\n        }\n\n        @Override\n        public void trace(String msg) {\n            log(LOG_LEVEL_TRACE, msg, null);\n        }\n\n        @Override\n        public void trace(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String msg, Throwable throwable) {\n            log(LOG_LEVEL_TRACE, msg, throwable);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return true;\n        }\n\n        @Override\n        public void debug(String msg) {\n            log(LOG_LEVEL_DEBUG, msg, null);\n        }\n\n        @Override\n        public void debug(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String msg, Throwable throwable) {\n            log(LOG_LEVEL_DEBUG, msg, throwable);\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return true;\n        }\n\n        @Override\n        public void info(String msg) {\n            log(LOG_LEVEL_INFO, msg, null);\n        }\n\n        @Override\n        public void info(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String msg, Throwable throwable) {\n            log(LOG_LEVEL_INFO, msg, throwable);\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return true;\n        }\n\n        @Override\n        public void warn(String msg) {\n            log(LOG_LEVEL_WARN, msg, null);\n        }\n\n        @Override\n        public void warn(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String msg, Throwable throwable) {\n            log(LOG_LEVEL_WARN, msg, throwable);\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return true;\n        }\n\n        @Override\n        public void error(String msg) {\n            log(LOG_LEVEL_ERROR, msg, null);\n        }\n\n        @Override\n        public void error(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String msg, Throwable throwable) {\n            log(LOG_LEVEL_ERROR, msg, throwable);\n        }\n    }\n}\n\nclass FormattingTuple {\n\n    static public FormattingTuple NULL = new FormattingTuple(null);\n\n    private String message;\n    private Throwable throwable;\n    private Object[] argArray;\n\n    public FormattingTuple(String message) {\n        this(message, null, null);\n    }\n\n    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {\n        this.message = message;\n        this.throwable = throwable;\n        this.argArray = argArray;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public Object[] getArgArray() {\n        return argArray;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n}\n\nfinal class MessageFormatter {\n    static final char DELIM_START = '{';\n    static final char DELIM_STOP = '}';\n    static final String DELIM_STR = \"{}\";\n    private static final char ESCAPE_CHAR = '\\\\';\n\n    /**\n     * Performs single argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi there.\".\n     * <p>\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg            The argument to be substituted in place of the formatting anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(String messagePattern, Object arg) {\n        return arrayFormat(messagePattern, new Object[]{arg});\n    }\n\n    /**\n     * Performs a two argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi Alice. My name is Bob.\".\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg1           The argument to be substituted in place of the first formatting\n     *                       anchor\n     * @param arg2           The argument to be substituted in place of the second formatting\n     *                       anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n        return arrayFormat(messagePattern, new Object[]{arg1, arg2});\n    }\n\n\n    static final Throwable getThrowableCandidate(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            return null;\n        }\n\n        final Object lastEntry = argArray[argArray.length - 1];\n        if (lastEntry instanceof Throwable) {\n            return (Throwable) lastEntry;\n        }\n        return null;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n        Throwable throwableCandidate = getThrowableCandidate(argArray);\n        Object[] args = argArray;\n        if (throwableCandidate != null) {\n            args = trimmedCopy(argArray);\n        }\n        return arrayFormat(messagePattern, args, throwableCandidate);\n    }\n\n    private static Object[] trimmedCopy(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            throw new IllegalStateException(\"non-sensical empty or null argument array\");\n        }\n        final int trimemdLen = argArray.length - 1;\n        Object[] trimmed = new Object[trimemdLen];\n        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);\n        return trimmed;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {\n\n        if (messagePattern == null) {\n            return new FormattingTuple(null, argArray, throwable);\n        }\n\n        if (argArray == null) {\n            return new FormattingTuple(messagePattern);\n        }\n\n        int i = 0;\n        int j;\n        // use string builder for better multicore performance\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n\n        int L;\n        for (L = 0; L < argArray.length; L++) {\n\n            j = messagePattern.indexOf(DELIM_STR, i);\n\n            if (j == -1) {\n                // no more variables\n                if (i == 0) { // this is a simple string\n                    return new FormattingTuple(messagePattern, argArray, throwable);\n                } else { // add the tail string which contains no variables and return\n                    // the result.\n                    sbuf.append(messagePattern, i, messagePattern.length());\n                    return new FormattingTuple(sbuf.toString(), argArray, throwable);\n                }\n            } else {\n                if (isEscapedDelimeter(messagePattern, j)) {\n                    if (!isDoubleEscaped(messagePattern, j)) {\n                        L--; // DELIM_START was escaped, thus should not be incremented\n                        sbuf.append(messagePattern, i, j - 1);\n                        sbuf.append(DELIM_START);\n                        i = j + 1;\n                    } else {\n                        // The escape character preceding the delimiter start is\n                        // itself escaped: \"abc x:\\\\{}\"\n                        // we have to consume one backward slash\n                        sbuf.append(messagePattern, i, j - 1);\n                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                        i = j + 2;\n                    }\n                } else {\n                    // normal case\n                    sbuf.append(messagePattern, i, j);\n                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                    i = j + 2;\n                }\n            }\n        }\n        // append the characters following the last {} pair.\n        sbuf.append(messagePattern, i, messagePattern.length());\n        return new FormattingTuple(sbuf.toString(), argArray, throwable);\n    }\n\n    final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {\n\n        if (delimeterStartIndex == 0) {\n            return false;\n        }\n        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);\n        if (potentialEscape == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n        if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // special treatment of array values was suggested by 'lizongbo'\n    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {\n        if (o == null) {\n            sbuf.append(\"null\");\n            return;\n        }\n        if (!o.getClass().isArray()) {\n            safeObjectAppend(sbuf, o);\n        } else {\n            // check for primitive array types because they\n            // unfortunately cannot be cast to Object[]\n            if (o instanceof boolean[]) {\n                booleanArrayAppend(sbuf, (boolean[]) o);\n            } else if (o instanceof byte[]) {\n                byteArrayAppend(sbuf, (byte[]) o);\n            } else if (o instanceof char[]) {\n                charArrayAppend(sbuf, (char[]) o);\n            } else if (o instanceof short[]) {\n                shortArrayAppend(sbuf, (short[]) o);\n            } else if (o instanceof int[]) {\n                intArrayAppend(sbuf, (int[]) o);\n            } else if (o instanceof long[]) {\n                longArrayAppend(sbuf, (long[]) o);\n            } else if (o instanceof float[]) {\n                floatArrayAppend(sbuf, (float[]) o);\n            } else if (o instanceof double[]) {\n                doubleArrayAppend(sbuf, (double[]) o);\n            } else {\n                objectArrayAppend(sbuf, (Object[]) o, seenMap);\n            }\n        }\n    }\n\n    private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n        try {\n            String oAsString = o.toString();\n            sbuf.append(oAsString);\n        } catch (Throwable t) {\n            sbuf.append(\"[FAILED toString()]\");\n        }\n\n    }\n\n    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n        sbuf.append('[');\n        if (!seenMap.containsKey(a)) {\n            seenMap.put(a, null);\n            final int len = a.length;\n            for (int i = 0; i < len; i++) {\n                deeplyAppendParameter(sbuf, a[i], seenMap);\n                if (i != len - 1)\n                    sbuf.append(\", \");\n            }\n            // allow repeats in siblings\n            seenMap.remove(a);\n        } else {\n            sbuf.append(\"...\");\n        }\n        sbuf.append(']');\n    }\n\n    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/HostApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport static android.os.Process.myPid;\n\nimport android.app.ActivityManager;\nimport android.app.Application;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.StrictMode;\nimport android.webkit.WebView;\n\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.dynamic.host.DynamicRuntime;\nimport com.tencent.shadow.dynamic.host.PluginManager;\nimport com.tencent.shadow.sample.host.lib.HostUiLayerProvider;\nimport com.tencent.shadow.sample.host.manager.Shadow;\n\nimport java.io.File;\n\npublic class HostApplication extends Application {\n    private static HostApplication sApp;\n\n    private PluginManager mPluginManager;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sApp = this;\n\n        detectNonSdkApiUsageOnAndroidP();\n        setWebViewDataDirectorySuffix();\n        LoggerFactory.setILoggerFactory(new AndroidLogLoggerFactory());\n\n        if (isProcess(this, \":plugin\")) {\n            //在全动态架构中，Activity组件没有打包在宿主而是位于被动态加载的runtime，\n            //为了防止插件crash后，系统自动恢复crash前的Activity组件，此时由于没有加载runtime而发生classNotFound异常，导致二次crash\n            //因此这里恢复加载上一次的runtime\n            DynamicRuntime.recoveryRuntime(this);\n        }\n\n        if (isProcess(this, getPackageName())) {\n            PluginHelper.getInstance().init(this);\n        }\n\n        HostUiLayerProvider.init(this);\n    }\n\n    private static void setWebViewDataDirectorySuffix() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        WebView.setDataDirectorySuffix(Application.getProcessName());\n    }\n\n    private static void detectNonSdkApiUsageOnAndroidP() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();\n        builder.detectNonSdkApiUsage();\n        StrictMode.setVmPolicy(builder.build());\n    }\n\n    public static HostApplication getApp() {\n        return sApp;\n    }\n\n    public void loadPluginManager(File apk) {\n        if (mPluginManager == null) {\n            mPluginManager = Shadow.getPluginManager(apk);\n        }\n    }\n\n    public PluginManager getPluginManager() {\n        return mPluginManager;\n    }\n\n    private static boolean isProcess(Context context, String processName) {\n        String currentProcName = \"\";\n        ActivityManager manager =\n                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {\n            if (processInfo.pid == myPid()) {\n                currentProcName = processInfo.processName;\n                break;\n            }\n        }\n\n        return currentProcName.endsWith(processName);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport static com.tencent.shadow.sample.constant.Constant.PART_KEY_PLUGIN_BASE;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.Spinner;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.constant.Constant;\nimport com.tencent.shadow.sample.host.plugin_view.HostAddPluginViewActivity;\n\n\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setTheme(R.style.TestHostTheme);\n\n        LinearLayout rootView = new LinearLayout(this);\n        rootView.setOrientation(LinearLayout.VERTICAL);\n\n        TextView infoTextView = new TextView(this);\n        infoTextView.setText(R.string.main_activity_info);\n        rootView.addView(infoTextView);\n\n        final Spinner partKeySpinner = new Spinner(this);\n        ArrayAdapter<String> partKeysAdapter = new ArrayAdapter<>(this, R.layout.part_key_adapter);\n        partKeysAdapter.addAll(\n                Constant.PART_KEY_PLUGIN_MAIN_APP,\n                Constant.PART_KEY_PLUGIN_ANOTHER_APP\n        );\n        partKeySpinner.setAdapter(partKeysAdapter);\n\n        rootView.addView(partKeySpinner);\n\n        Button startPluginButton = new Button(this);\n        startPluginButton.setText(R.string.start_plugin);\n        startPluginButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String partKey = (String) partKeySpinner.getSelectedItem();\n                Intent intent = new Intent(MainActivity.this, PluginLoadActivity.class);\n                switch (partKey) {\n                    //为了演示多进程多插件，其实两个插件内容完全一样，除了所在进程\n                    case Constant.PART_KEY_PLUGIN_MAIN_APP:\n                        intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, PART_KEY_PLUGIN_BASE);\n                        break;\n                    case Constant.PART_KEY_PLUGIN_ANOTHER_APP:\n                        intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);\n                        ;\n                        break;\n                }\n\n                switch (partKey) {\n                    //为了演示多进程多插件，其实两个插件内容完全一样，除了所在进程\n                    case Constant.PART_KEY_PLUGIN_MAIN_APP:\n                    case Constant.PART_KEY_PLUGIN_ANOTHER_APP:\n                        intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, \"com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity\");\n                        break;\n\n                }\n                startActivity(intent);\n            }\n        });\n        rootView.addView(startPluginButton);\n\n        Button startHostAddPluginViewActivityButton = new Button(this);\n        startHostAddPluginViewActivityButton.setText(\"宿主添加插件View\");\n        startHostAddPluginViewActivityButton.setOnClickListener(v -> {\n            Intent intent = new Intent(this, HostAddPluginViewActivity.class);\n            startActivity(intent);\n        });\n        rootView.addView(startHostAddPluginViewActivityButton);\n\n        setContentView(rootView);\n\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/Plugin2ProcessPPS.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.Resources;\nimport android.util.Log;\n\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\nimport com.tencent.shadow.sample.host.lib.LoadPluginCallback;\n\npublic class Plugin2ProcessPPS extends PluginProcessService {\n    public Plugin2ProcessPPS() {\n        LoadPluginCallback.setCallback(new LoadPluginCallback.Callback() {\n\n            @Override\n            public void beforeLoadPlugin(String partKey) {\n                Log.d(\"Plugin2ProcessPPS\", \"beforeLoadPlugin(\" + partKey + \")\");\n            }\n\n            @Override\n            public void afterLoadPlugin(String partKey, ApplicationInfo applicationInfo, ClassLoader pluginClassLoader, Resources pluginResources) {\n                Log.d(\"Plugin2ProcessPPS\", \"afterLoadPlugin(\" + partKey + \",\" + applicationInfo.className + \"{metaData=\" + applicationInfo.metaData + \"}\" + \",\" + pluginClassLoader + \")\");\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.content.Context;\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class PluginHelper {\n\n    /**\n     * 动态加载的插件管理apk\n     */\n    public final static String sPluginManagerName = \"pluginmanager.apk\";\n\n    /**\n     * 动态加载的插件包，里面包含以下几个部分，插件apk，插件框架apk（loader apk和runtime apk）, apk信息配置关系json文件\n     */\n    public final static String sPluginZip = BuildConfig.DEBUG ? \"plugin-debug.zip\" : \"plugin-release.zip\";\n\n    public File pluginManagerFile;\n\n    public File pluginZipFile;\n\n    public ExecutorService singlePool = Executors.newSingleThreadExecutor();\n\n    private Context mContext;\n\n    private static PluginHelper sInstance = new PluginHelper();\n\n    public static PluginHelper getInstance() {\n        return sInstance;\n    }\n\n    private PluginHelper() {\n    }\n\n    public void init(Context context) {\n        pluginManagerFile = new File(context.getFilesDir(), sPluginManagerName);\n        pluginZipFile = new File(context.getFilesDir(), sPluginZip);\n\n        mContext = context.getApplicationContext();\n\n        singlePool.execute(new Runnable() {\n            @Override\n            public void run() {\n                preparePlugin();\n            }\n        });\n\n    }\n\n    private void preparePlugin() {\n        try {\n\n            //noinspection ResultOfMethodCallIgnored\n            pluginManagerFile.setWritable(true);\n\n            InputStream is = mContext.getAssets().open(sPluginManagerName);\n            FileUtils.copyInputStreamToFile(is, pluginManagerFile);\n\n            InputStream zip = mContext.getAssets().open(sPluginZip);\n            FileUtils.copyInputStreamToFile(zip, pluginZipFile);\n\n        } catch (IOException e) {\n            throw new RuntimeException(\"从assets中复制apk出错\", e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginLoadActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.sample.constant.Constant;\n\n\npublic class PluginLoadActivity extends Activity {\n\n    private ViewGroup mViewGroup;\n\n    private Handler mHandler = new Handler();\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_load);\n\n        mViewGroup = findViewById(R.id.container);\n\n        startPlugin();\n    }\n\n\n    public void startPlugin() {\n\n        PluginHelper.getInstance().singlePool.execute(new Runnable() {\n            @Override\n            public void run() {\n                HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);\n\n                Bundle bundle = new Bundle();\n                bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());\n                bundle.putString(Constant.KEY_PLUGIN_PART_KEY, getIntent().getStringExtra(Constant.KEY_PLUGIN_PART_KEY));\n                bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));\n\n                HostApplication.getApp().getPluginManager()\n                        .enter(PluginLoadActivity.this, Constant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() {\n                            @Override\n                            public void onShowLoadingView(final View view) {\n                                mHandler.post(new Runnable() {\n                                    @Override\n                                    public void run() {\n                                        mViewGroup.addView(view);\n                                    }\n                                });\n                            }\n\n                            @Override\n                            public void onCloseLoadingView() {\n                                finish();\n                            }\n\n                            @Override\n                            public void onEnterComplete() {\n\n                            }\n                        });\n            }\n        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        HostApplication.getApp().getPluginManager().enter(this, Constant.FROM_ID_CLOSE, null, null);\n        mViewGroup.removeAllViews();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginProcessPPS.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host;\n\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.Resources;\nimport android.util.Log;\n\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\nimport com.tencent.shadow.sample.host.lib.LoadPluginCallback;\n\npublic class PluginProcessPPS extends PluginProcessService {\n    public PluginProcessPPS() {\n        LoadPluginCallback.setCallback(new LoadPluginCallback.Callback() {\n\n            @Override\n            public void beforeLoadPlugin(String partKey) {\n                Log.d(\"PluginProcessPPS\", \"beforeLoadPlugin(\" + partKey + \")\");\n            }\n\n            @Override\n            public void afterLoadPlugin(String partKey, ApplicationInfo applicationInfo, ClassLoader pluginClassLoader, Resources pluginResources) {\n                Log.d(\"PluginProcessPPS\", \"afterLoadPlugin(\" + partKey + \",\" + applicationInfo.className + \"{metaData=\" + applicationInfo.metaData + \"}\" + \",\" + pluginClassLoader + \")\");\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/manager/FixedPathPmUpdater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host.manager;\n\nimport android.os.Build;\n\nimport com.tencent.shadow.dynamic.host.PluginManagerUpdater;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\npublic class FixedPathPmUpdater implements PluginManagerUpdater {\n\n    final private File apk;\n\n    FixedPathPmUpdater(File apk) {\n        this.apk = apk;\n\n        //在API 33以上的系统上，禁止动态加载文件可写入，满足系统安全限制\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {\n            //noinspection ResultOfMethodCallIgnored\n            apk.setWritable(false);\n        }\n    }\n\n\n    @Override\n    public boolean wasUpdating() {\n        return false;\n    }\n\n    @Override\n    public Future<File> update() {\n        return null;\n    }\n\n    @Override\n    public File getLatest() {\n        return apk;\n    }\n\n    @Override\n    public Future<Boolean> isAvailable(final File file) {\n        return null;\n    }\n}"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/manager/Shadow.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host.manager;\n\nimport com.tencent.shadow.dynamic.host.DynamicPluginManager;\nimport com.tencent.shadow.dynamic.host.PluginManager;\n\nimport java.io.File;\n\npublic class Shadow {\n\n    public static PluginManager getPluginManager(File apk) {\n        final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk);\n        File tempPm = fixedPathPmUpdater.getLatest();\n        if (tempPm != null) {\n            return new DynamicPluginManager(fixedPathPmUpdater);\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/plugin_view/HostAddPluginViewActivity.java",
    "content": "package com.tencent.shadow.sample.host.plugin_view;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.host.lib.HostAddPluginViewContainer;\nimport com.tencent.shadow.sample.host.lib.HostAddPluginViewContainerHolder;\n\npublic class HostAddPluginViewActivity extends Activity implements HostAddPluginViewContainer {\n    private ViewGroup mPluginViewContainer;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        LinearLayout activityContentView = new LinearLayout(this);\n        activityContentView.setOrientation(LinearLayout.VERTICAL);\n\n        LinearLayout.LayoutParams wrapContent = new LinearLayout.LayoutParams(\n                ViewGroup.LayoutParams.WRAP_CONTENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT\n        );\n\n        TextView note = new TextView(this);\n        note.setLayoutParams(wrapContent);\n        note.setText(\"需要先启动插件sample-plugin-app后，才能点下面的加载插件View\");\n\n        Button loadButton = new Button(this);\n        loadButton.setText(\"加载插件View\");\n        loadButton.setOnClickListener(this::loadPluginView);\n        loadButton.setLayoutParams(wrapContent);\n\n        ViewGroup pluginViewContainer = new LinearLayout(this);\n        pluginViewContainer.setLayoutParams(wrapContent);\n        mPluginViewContainer = pluginViewContainer;\n\n        View[] views = {\n                note,\n                loadButton,\n                pluginViewContainer\n        };\n        for (View view : views) {\n            activityContentView.addView(view);\n        }\n        setContentView(activityContentView);\n    }\n\n    private void loadPluginView(View view) {\n        //简化逻辑，只允许点一次\n        view.setEnabled(false);\n\n        //因为当前Activity和插件都在:plugin进程，不能直接操作主进程的manager对象，所以通过一个广播调用manager。\n        Intent intent = new Intent();\n        intent.setPackage(getPackageName());\n        intent.setAction(\"sample_host.manager.startPluginService\");\n\n        final int id = System.identityHashCode(this);\n        HostAddPluginViewContainerHolder.instances.put(id, this);\n        intent.putExtra(\"id\", id);\n\n        sendBroadcast(intent);\n    }\n\n    @Override\n    public void addView(View view) {\n        mPluginViewContainer.addView(view);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/plugin_view/MainProcessManagerReceiver.java",
    "content": "package com.tencent.shadow.sample.host.plugin_view;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport com.tencent.shadow.sample.constant.Constant;\nimport com.tencent.shadow.sample.host.HostApplication;\n\npublic class MainProcessManagerReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        HostApplication.getApp().getPluginManager()\n                .enter(context, Constant.FROM_ID_LOAD_VIEW_TO_HOST, intent.getExtras(), null);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/res/layout/activity_jump_to_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <Button\n        android:id=\"@+id/jump\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"jump\"\n        android:text=\"跳转\" />\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/res/layout/activity_load.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/container\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n</FrameLayout>"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/res/layout/part_key_adapter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textColor=\"@android:color/black\" />"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <string name=\"main_activity_info\">\n        这是一个全动态的测试程序，插件管理（test-dynamic-manager）,\n        插件框架（test-dynamic-loader及test-dynamic-runtime），\n        以及插件本身,都是动态加载的\n    </string>\n\n    <string name=\"start_plugin\">\n        启动插件\n    </string>\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-host/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"TestHostTheme\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\" />\n\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-host-lib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'com.tencent.shadow.internal.aar-to-jar'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n            consumerProguardFiles 'sample-host-lib.pro'\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/sample-host-lib.pro",
    "content": "#让宿主在打包时能够keep住插件要使用到的类名和方法\n-keep class com.tencent.shadow.sample.host.lib.*{\n    public *;\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.sample.host.lib\" />\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/java/com/tencent/shadow/sample/host/lib/HostAddPluginViewContainer.java",
    "content": "package com.tencent.shadow.sample.host.lib;\n\nimport android.view.View;\n\npublic interface HostAddPluginViewContainer {\n    void addView(View view);\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/java/com/tencent/shadow/sample/host/lib/HostAddPluginViewContainerHolder.java",
    "content": "package com.tencent.shadow.sample.host.lib;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class HostAddPluginViewContainerHolder {\n    public final static Map<Integer, HostAddPluginViewContainer> instances = new HashMap<>();\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/java/com/tencent/shadow/sample/host/lib/HostUiLayerProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.host.lib;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\n/**\n * 这是一个将要打包到宿主中的类。原本的目的是宿主依赖插件，宿主\n */\npublic class HostUiLayerProvider {\n    private static HostUiLayerProvider sInstance;\n\n    public static void init(Context mHostApplicationContext) {\n        sInstance = new HostUiLayerProvider(mHostApplicationContext);\n    }\n\n    public static HostUiLayerProvider getInstance() {\n        return sInstance;\n    }\n\n    final private Context mHostApplicationContext;\n\n    private HostUiLayerProvider(Context mHostApplicationContext) {\n        this.mHostApplicationContext = mHostApplicationContext;\n    }\n\n    public View buildHostUiLayer() {\n        return LayoutInflater.from(mHostApplicationContext)\n                .inflate(R.layout.host_ui_layer_layout, null, false);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/java/com/tencent/shadow/sample/host/lib/LoadPluginCallback.java",
    "content": "package com.tencent.shadow.sample.host.lib;\n\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.Resources;\n\npublic class LoadPluginCallback {\n\n    private static Callback sCallback;\n\n    public static void setCallback(Callback callback) {\n        sCallback = callback;\n    }\n\n    public static Callback getCallback() {\n        return sCallback;\n    }\n\n    public interface Callback {\n        void beforeLoadPlugin(String partKey);\n\n        void afterLoadPlugin(String partKey, ApplicationInfo applicationInfo, ClassLoader pluginClassLoader, Resources pluginResources);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-host-lib/src/main/res/layout/host_ui_layer_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/textView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textColor=\"@android:color/holo_red_dark\"\n        android:text=\"Host UI Layer\" />\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-manager/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-manager/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.dynamic:dynamic-manager'\n    implementation 'com.tencent.shadow.core:manager'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader'\n    implementation project(':sample-constant')\n\n    compileOnly 'com.tencent.shadow.core:common'\n    compileOnly 'com.tencent.shadow.dynamic:dynamic-host'\n}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.manager\" />\n\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/ManagerFactoryImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.dynamic.host.ManagerFactory;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.sample.manager.SamplePluginManager;\n\n/**\n * 此类包名及类名固定\n */\npublic final class ManagerFactoryImpl implements ManagerFactory {\n    @Override\n    public PluginManagerImpl buildManager(Context context) {\n        return new SamplePluginManager(context);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\n/**\n * 此类包名及类名固定\n * classLoader的白名单\n * PluginManager可以加载宿主中位于白名单内的类\n */\npublic interface WhiteList {\n    String[] sWhiteList = new String[]\n            {\n                    \"com.tencent.host.shadow\",\n                    \"com.tencent.shadow.test.lib.constant\",\n            };\n}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/java/com/tencent/shadow/sample/manager/FastPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.manager;\n\nimport android.content.Context;\nimport android.os.RemoteException;\nimport android.util.Pair;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.core.manager.installplugin.InstalledType;\nimport com.tencent.shadow.core.manager.installplugin.PluginConfig;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.manager.PluginManagerThatUseDynamicLoader;\n\nimport org.json.JSONException;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\npublic abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(FastPluginManager.class);\n\n    private ExecutorService mFixedPool = Executors.newFixedThreadPool(4);\n\n    public FastPluginManager(Context context) {\n        super(context);\n    }\n\n\n    public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {\n        final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash);\n        final String uuid = pluginConfig.UUID;\n        List<Future> futures = new LinkedList<>();\n        List<Future<Pair<String, String>>> extractSoFutures = new LinkedList<>();\n        if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) {\n            Future odexRuntime = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME,\n                            pluginConfig.runTime.file);\n                    return null;\n                }\n            });\n            futures.add(odexRuntime);\n            Future odexLoader = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER,\n                            pluginConfig.pluginLoader.file);\n                    return null;\n                }\n            });\n            futures.add(odexLoader);\n        }\n        for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : pluginConfig.plugins.entrySet()) {\n            final String partKey = plugin.getKey();\n            final File apkFile = plugin.getValue().file;\n            Future<Pair<String, String>> extractSo = mFixedPool.submit(() -> extractSo(uuid, partKey, apkFile));\n            futures.add(extractSo);\n            extractSoFutures.add(extractSo);\n            if (odex) {\n                Future odexPlugin = mFixedPool.submit(new Callable() {\n                    @Override\n                    public Object call() throws Exception {\n                        oDexPlugin(uuid, partKey, apkFile);\n                        return null;\n                    }\n                });\n                futures.add(odexPlugin);\n            }\n        }\n\n        for (Future future : futures) {\n            future.get();\n        }\n        Map<String, String> soDirMap = new HashMap<>();\n        for (Future<Pair<String, String>> future : extractSoFutures) {\n            Pair<String, String> pair = future.get();\n            soDirMap.put(pair.first, pair.second);\n        }\n        onInstallCompleted(pluginConfig, soDirMap);\n\n        return getInstalledPlugins(1).get(0);\n    }\n\n\n    protected void callApplicationOnCreate(String partKey) throws RemoteException {\n        Map map = mPluginLoader.getLoadedPlugin();\n        Boolean isCall = (Boolean) map.get(partKey);\n        if (isCall == null || !isCall) {\n            mPluginLoader.callApplicationOnCreate(partKey);\n        }\n    }\n\n    private void loadPluginLoaderAndRuntime(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {\n        if (mPpsController == null) {\n            bindPluginProcessService(getPluginProcessServiceName(partKey));\n            waitServiceConnected(10, TimeUnit.SECONDS);\n        }\n        loadRunTime(uuid);\n        loadPluginLoader(uuid);\n    }\n\n    protected void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {\n        loadPluginLoaderAndRuntime(uuid, partKey);\n        Map map = mPluginLoader.getLoadedPlugin();\n        if (!map.containsKey(partKey)) {\n            mPluginLoader.loadPlugin(partKey);\n        }\n    }\n\n\n    protected abstract String getPluginProcessServiceName(String partKey);\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/java/com/tencent/shadow/sample/manager/SamplePluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.manager;\n\nimport static com.tencent.shadow.sample.constant.Constant.PART_KEY_PLUGIN_ANOTHER_APP;\nimport static com.tencent.shadow.sample.constant.Constant.PART_KEY_PLUGIN_BASE;\nimport static com.tencent.shadow.sample.constant.Constant.PART_KEY_PLUGIN_MAIN_APP;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.RemoteException;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.sample.constant.Constant;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n\npublic class SamplePluginManager extends FastPluginManager {\n\n    private ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n    private Context mCurrentContext;\n\n    public SamplePluginManager(Context context) {\n        super(context);\n        mCurrentContext = context;\n    }\n\n    /**\n     * @return PluginManager实现的别名，用于区分不同PluginManager实现的数据存储路径\n     */\n    @Override\n    protected String getName() {\n        return \"test-dynamic-manager\";\n    }\n\n    /**\n     * @return 宿主中注册的PluginProcessService实现的类名\n     */\n    @Override\n    protected String getPluginProcessServiceName(String partKey) {\n        if (PART_KEY_PLUGIN_MAIN_APP.equals(partKey)) {\n            return \"com.tencent.shadow.sample.host.PluginProcessPPS\";\n        } else if (PART_KEY_PLUGIN_BASE.equals(partKey)) {\n            return \"com.tencent.shadow.sample.host.PluginProcessPPS\";\n        } else if (PART_KEY_PLUGIN_ANOTHER_APP.equals(partKey)) {\n            return \"com.tencent.shadow.sample.host.Plugin2ProcessPPS\";//在这里支持多个插件\n        } else {\n            //如果有默认PPS，可用return代替throw\n            throw new IllegalArgumentException(\"unexpected plugin load request: \" + partKey);\n        }\n    }\n\n    @Override\n    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {\n        if (fromId == Constant.FROM_ID_NOOP) {\n            //do nothing.\n        } else if (fromId == Constant.FROM_ID_START_ACTIVITY) {\n            onStartActivity(context, bundle, callback);\n        } else if (fromId == Constant.FROM_ID_CLOSE) {\n            close();\n        } else if (fromId == Constant.FROM_ID_LOAD_VIEW_TO_HOST) {\n            loadViewToHost(context, bundle);\n        } else {\n            throw new IllegalArgumentException(\"不认识的fromId==\" + fromId);\n        }\n    }\n\n    private void loadViewToHost(final Context context, Bundle bundle) {\n        Intent pluginIntent = new Intent();\n        pluginIntent.setClassName(\n                context.getPackageName(),\n                \"com.tencent.shadow.sample.plugin.app.lib.usecases.service.HostAddPluginViewService\"\n        );\n        pluginIntent.putExtras(bundle);\n        try {\n            mPluginLoader.startPluginService(pluginIntent);\n        } catch (RemoteException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) {\n        final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH);\n        final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY);\n        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);\n        if (className == null) {\n            throw new NullPointerException(\"className == null\");\n        }\n        final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);\n\n        if (callback != null) {\n            final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);\n            callback.onShowLoadingView(view);\n        }\n\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);\n\n                    loadPlugin(installedPlugin.UUID, PART_KEY_PLUGIN_BASE);\n                    loadPlugin(installedPlugin.UUID, PART_KEY_PLUGIN_MAIN_APP);\n                    callApplicationOnCreate(PART_KEY_PLUGIN_BASE);\n                    callApplicationOnCreate(PART_KEY_PLUGIN_MAIN_APP);\n\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(\n                            context.getPackageName(),\n                            className\n                    );\n                    if (extras != null) {\n                        pluginIntent.replaceExtras(extras);\n                    }\n                    Intent intent = mPluginLoader.convertActivityIntent(pluginIntent);\n                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                    mPluginLoader.startActivityInPluginProcess(intent);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n                if (callback != null) {\n                    callback.onCloseLoadingView();\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-manager/src/main/res/layout/activity_load_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"#fcfcfc\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/black\"\n        android:layout_marginTop=\"20dp\"\n        android:textSize=\"30sp\"\n        android:text=\"这是一个位于dynamic-pluginmanager-apk中的view\" />\n\n    <ProgressBar\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/build.gradle",
    "content": "buildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n\n    dependencies {\n        classpath 'com.tencent.shadow.core:runtime'\n        classpath 'com.tencent.shadow.core:activity-container'\n        classpath 'com.tencent.shadow.core:gradle-plugin'\n        classpath \"org.javassist:javassist:$javassist_version\"\n    }\n}\n\napply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId 'com.tencent.shadow.sample.plugin.app'\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n}\n\ndependencies {\n    //注意sample-host-lib要用compileOnly编译而不打包在插件中。在packagePlugin任务中配置hostWhiteList允许插件访问宿主的类。\n    pluginCompileOnly project(\":sample-host-lib\")\n    normalImplementation project(\":sample-host-lib\")\n\n    pluginCompileOnly project(\":sample-base-lib\")\n    normalImplementation project(\":sample-base-lib\")\n\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly 'com.tencent.shadow.core:runtime'\n}\n\npreBuild.dependsOn(\":sample-host-lib:jarDebugPackage\")\n\n\ndef createDuplicateApkTask(buildType) {\n    def apkDir = file(\"${getBuildDir()}/outputs/apk/plugin/$buildType\")\n\n    return tasks.create(\"duplicatePlugin${buildType.capitalize()}ApkTask\", Copy) {\n        group = 'build'\n        description = \"复制一个sample-app-plugin-${buildType}.apk用于测试目的\"\n        from(apkDir) {\n            include(\"sample-app-plugin-${buildType}.apk\")\n            rename { \"sample-app-plugin-${buildType}2.apk\" }\n        }\n        into(apkDir)\n\n    }.dependsOn(\":sample-app:assemblePlugin${buildType.capitalize()}\")\n}\n\ntasks.whenTaskAdded { task ->\n    if (task.name == \"assemblePluginDebug\") {\n        def createTask = createDuplicateApkTask('debug')\n        task.finalizedBy(createTask)\n    }\n    if (task.name == \"assemblePluginRelease\") {\n        def createTask = createDuplicateApkTask('release')\n        task.finalizedBy(createTask)\n    }\n}\n\n\nshadow {\n    transform {\n//        useHostContext = ['abc']\n    }\n\n    packagePlugin {\n        pluginTypes {\n            debug {\n                loaderApkConfig = new Tuple2('sample-loader-debug.apk', ':sample-loader:assembleDebug')\n                runtimeApkConfig = new Tuple2('sample-runtime-debug.apk', ':sample-runtime:assembleDebug')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'sample-plugin-app'\n                        partKey = 'sample-plugin-app'\n                        buildTask = ':sample-app:assemblePluginDebug'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-app/build/outputs/apk/plugin/debug/sample-app-plugin-debug.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                        dependsOn = ['sample-base']\n                    }\n                    pluginApk2 {\n                        businessName = 'sample-plugin-app2'\n                        partKey = 'sample-plugin-app2'\n                        buildTask = ':sample-app:assemblePluginDebug'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-app/build/outputs/apk/plugin/debug/sample-app-plugin-debug2.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                        dependsOn = ['sample-base']\n                    }\n                    sampleBase {\n                        businessName = 'sample-plugin-app'\n                        partKey = 'sample-base'\n                        buildTask = ':sample-base:assemblePluginDebug'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-base/build/outputs/apk/plugin/debug/sample-base-plugin-debug.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                    }\n                }\n            }\n\n            release {\n                loaderApkConfig = new Tuple2('sample-loader-release.apk', ':sample-loader:assembleRelease')\n                runtimeApkConfig = new Tuple2('sample-runtime-release.apk', ':sample-runtime:assembleRelease')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'sample-plugin-app'\n                        partKey = 'sample-plugin-app'\n                        buildTask = ':sample-app:assemblePluginRelease'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-app/build/outputs/apk/plugin/release/sample-app-plugin-release.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                        dependsOn = ['sample-base']\n                    }\n                    pluginApk2 {\n                        businessName = 'sample-plugin-app2'\n                        partKey = 'sample-plugin-app2'\n                        buildTask = ':sample-app:assemblePluginRelease'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-app/build/outputs/apk/plugin/release/sample-app-plugin-release2.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                        dependsOn = ['sample-base']\n                    }\n                    sampleBase {\n                        businessName = 'sample-plugin-app'\n                        partKey = 'sample-base'\n                        buildTask = ':sample-base:assemblePluginRelease'\n                        apkPath = 'projects/sample/source/sample-plugin/sample-base/build/outputs/apk/plugin/release/sample-base-plugin-release.apk'\n                        hostWhiteList = [\"com.tencent.shadow.sample.host.lib\"]\n                    }\n                }\n            }\n        }\n\n        loaderApkProjectPath = 'projects/sample/source/sample-plugin/sample-loader'\n        runtimeApkProjectPath = 'projects/sample/source/sample-plugin/sample-runtime'\n\n        archiveSuffix = System.getenv(\"PluginSuffix\") ?: \"\"\n        archivePrefix = 'plugin'\n        destinationDir = \"${getRootProject().getBuildDir()}\"\n\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n#这是Shadow在编译期将AndroidManifest.xml中所需信息生成的Java类，没有被代码自然引用，所以需要手工keep住。\n-keep class com.tencent.shadow.core.manifest_parser.PluginManifest{*;}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.plugin.app.lib\">\n\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <application\n        android:name=\"com.tencent.shadow.sample.plugin.app.lib.UseCaseApplication\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:theme=\"@android:style/Theme.NoTitleBar\"\n        android:label=\"@string/app_name\">\n\n        <meta-data\n            android:name=\"test_meta\"\n            android:value=\"test_value\" />\n\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreate\" />\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivitySetTheme\" />\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOptionMenu\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOnCreate\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOrientation\"\n            android:configChanges=\"orientation|screenSize|fontScale\"\n            android:screenOrientation=\"landscape\"\n            android:windowSoftInputMode=\"adjustResize\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityWindowSoftMode\"\n            android:windowSoftInputMode=\"stateVisible\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestDBContentProviderActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreateBySystem\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestReceiverActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestDynamicReceiverActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestDynamicFragmentActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestXmlFragmentActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.dialog.TestDialogActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.packagemanager.TestPackageManagerActivity\" />\n\n        <receiver android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver\">\n            <intent-filter>\n                <action android:name=\"com.tencent.test.action\" />\n            </intent-filter>\n        </receiver>\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestFileProviderActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.application.TestApplicationActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.context.ActivityContextSubDirTestActivity\" />\n\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.context.ApplicationContextSubDirTestActivity\" />\n        <activity android:name=\".usecases.host_communication.PluginUseHostClassActivity\" />\n        <activity android:name=\".usecases.webview.WebViewActivity\" />\n        <activity android:name=\".usecases.fragment.TestDialogFragmentActivity\" />\n\n        <provider\n            android:authorities=\"${applicationId}.provider.test\"\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestProvider\" />\n\n        <service android:name=\".usecases.service.HostAddPluginViewService\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/assets/web/test.html",
    "content": "<html>\n<body>\nlocation.search:\n\n<script type=\"text/javascript\">\ndocument.write(location.search);\n\n</script>\n\n<select name=\"select_name\" id=\"select_id\">\n    <option value=\"a\">a</option>\n    <option value=\"b\">b</option>\n</select>\n\n</body>\n</html>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/UseCaseApplication.java",
    "content": "package com.tencent.shadow.sample.plugin.app.lib;\n\nimport static com.tencent.shadow.sample.plugin.app.lib.gallery.cases.UseCaseManager.useCases;\n\nimport android.app.Application;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.UseCaseManager;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCaseCategory;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOnCreate;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOptionMenu;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOrientation;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreate;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreateBySystem;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivitySetTheme;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityWindowSoftMode;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.context.ActivityContextSubDirTestActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.context.ApplicationContextSubDirTestActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.dialog.TestDialogActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestDialogFragmentActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestDynamicFragmentActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestXmlFragmentActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.host_communication.PluginUseHostClassActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.packagemanager.TestPackageManagerActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestDBContentProviderActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestFileProviderActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestDynamicReceiverActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestReceiverActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.usecases.webview.WebViewActivity;\n\npublic class UseCaseApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        initCase();\n    }\n\n    private static void initCase() {\n\n        if (UseCaseManager.sInit) {\n            throw new RuntimeException(\"不能重复调用init\");\n        }\n\n        UseCaseManager.sInit = true;\n\n        UseCaseCategory activityCategory = new UseCaseCategory(\"Activity测试用例\", new UseCase[]{\n                new TestActivityOnCreate.Case(),\n                new TestActivityReCreate.Case(),\n                new TestActivityReCreateBySystem.Case(),\n                new TestActivityOrientation.Case(),\n                new TestActivityWindowSoftMode.Case(),\n                new TestActivitySetTheme.Case(),\n                new TestActivityOptionMenu.Case(),\n                new WebViewActivity.Case()\n        });\n        useCases.add(activityCategory);\n\n        UseCaseCategory broadcastReceiverCategory = new UseCaseCategory(\"广播测试用例\", new UseCase[]{\n                new TestReceiverActivity.Case(),\n                new TestDynamicReceiverActivity.Case()\n        });\n        useCases.add(broadcastReceiverCategory);\n\n\n        UseCaseCategory providerCategory = new UseCaseCategory(\"ContentProvider测试用例\", new UseCase[]{\n                new TestDBContentProviderActivity.Case(),\n                new TestFileProviderActivity.Case()\n        });\n        useCases.add(providerCategory);\n\n\n        UseCaseCategory fragmentCategory = new UseCaseCategory(\"fragment测试用例\", new UseCase[]{\n                new TestDynamicFragmentActivity.Case(),\n                new TestXmlFragmentActivity.Case(),\n                new TestDialogFragmentActivity.Case()\n        });\n        useCases.add(fragmentCategory);\n\n        UseCaseCategory dialogCategory = new UseCaseCategory(\"Dialog测试用例\", new UseCase[]{\n                new TestDialogActivity.Case(),\n        });\n        useCases.add(dialogCategory);\n\n        UseCaseCategory packageManagerCategory = new UseCaseCategory(\"PackageManager测试用例\", new UseCase[]{\n                new TestPackageManagerActivity.Case(),\n        });\n        useCases.add(packageManagerCategory);\n\n\n        UseCaseCategory contextCategory = new UseCaseCategory(\"Context相关测试用例\", new UseCase[]{\n                new ActivityContextSubDirTestActivity.Case(),\n                new ApplicationContextSubDirTestActivity.Case(),\n        });\n        useCases.add(contextCategory);\n\n        UseCaseCategory communicationCategory = new UseCaseCategory(\"插件和宿主通信相关测试用例\", new UseCase[]{\n                new PluginUseHostClassActivity.Case(),\n        });\n        useCases.add(communicationCategory);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityOnCreate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\npublic class TestActivityOnCreate extends Activity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"生命周期测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Activity的生命周期方法是否正确回调\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityOnCreate.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_activity_lifecycle);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        ToastUtil.showToast(this, \"onStart\");\n    }\n\n    @Override\n    protected void onRestart() {\n        super.onRestart();\n        ToastUtil.showToast(this, \"onRestart\");\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        ToastUtil.showToast(this, \"onResume\");\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        ToastUtil.showToast(this, \"onSaveInstanceState\");\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        ToastUtil.showToast(this, \"onRestoreInstanceState\");\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        ToastUtil.showToast(this, \"onStop\");\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        ToastUtil.showToast(this, \"onDestroy\");\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityOptionMenu.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.Menu;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestActivityOptionMenu extends Activity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"Activity Menu测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Activity的 onCreateOptionsMenu\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityOptionMenu.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        setTheme(R.style.PluginAppThemeLight);\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_activity_settheme);\n        setTitle(\"看右边的 menu ->\");\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.case_test_activity_option_menu, menu);\n        return super.onCreateOptionsMenu(menu);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityOrientation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.content.pm.ActivityInfo;\nimport android.content.res.Configuration;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\n\npublic class TestActivityOrientation extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"横竖屏切换测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试横竖屏切换时，Activity的生命周期变化是否和AndroidManifest.xml中配置的config相关\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityOrientation.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_orientation);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n\n    public void setOrientation(View view) {\n        int orientation = getRequestedOrientation();\n        if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {\n            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n        } else {\n            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        }\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        super.onConfigurationChanged(newConfig);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\npublic class TestActivityReCreate extends Activity {\n\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"ReCreate\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Activity的调用ReCreate是否工作正常\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityReCreate.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_recreate);\n        TextView textView = findViewById(R.id.tv_msg);\n        boolean isRecreate = getIntent().getBooleanExtra(\"reCreate\", false);\n        textView.setText(\"isRecreate:\" + isRecreate);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n    public void reCreate(View view) {\n        getIntent().putExtra(\"reCreate\", true);\n        recreate();\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        ToastUtil.showToast(this, \"onStart\");\n    }\n\n    @Override\n    protected void onRestart() {\n        super.onRestart();\n        ToastUtil.showToast(this, \"onRestart\");\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        ToastUtil.showToast(this, \"onResume\");\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        ToastUtil.showToast(this, \"onSaveInstanceState\");\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        ToastUtil.showToast(this, \"onRestoreInstanceState\");\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        ToastUtil.showToast(this, \"onStop\");\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        ToastUtil.showToast(this, \"onDestroy\");\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreateBySystem.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestActivityReCreateBySystem extends Activity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"ReCreateBySystem\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"不保留活动进行测试，需要手动到开发者模式中开启\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityReCreateBySystem.class;\n        }\n\n        @Override\n        public Bundle getPageParams() {\n            Bundle bundle = new Bundle();\n            bundle.putString(\"url\", \"https://www.baidu.com\");\n            return bundle;\n        }\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_test_re_create_by_system);\n        String url = \"url : \" + getIntent().getStringExtra(\"url\");\n        ((TextView) findViewById(R.id.url_tv)).setText(url);\n    }\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivitySetTheme.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\npublic class TestActivitySetTheme extends Activity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"Activity 主题测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Activity的 setTheme 方法\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivitySetTheme.class;\n        }\n    }\n\n    int currentTheme = 0;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        int currentTheme = getIntent().getIntExtra(\"theme\", 0);\n        currentTheme++;\n        setTheme(currentTheme % 2 == 0 ? R.style.TestPluginTheme : R.style.PluginAppThemeLight);\n        ToastUtil.showToast(TestActivitySetTheme.this, currentTheme % 2 == 0 ? \"R.style.TestPluginTheme\" : \"R.style.PluginAppThemeLight\");\n        //setTheme必须在super.onCreate之前调用才行\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_activity_settheme);\n        final View btn = findViewById(R.id.button);\n        btn.setEnabled(true);\n        final int finalCurrentTheme = currentTheme;\n        btn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                btn.setEnabled(false);\n\n                Intent intent = new Intent(TestActivitySetTheme.this, TestActivitySetTheme.class);\n                intent.putExtra(\"theme\", finalCurrentTheme);\n                startActivity(intent);\n\n                btn.setEnabled(true);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityWindowSoftMode.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.activity;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.WindowManager;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\n\npublic class TestActivityWindowSoftMode extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"windowSoftInputMode测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试插件中设置windowSoftInputMode是否生效\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestActivityWindowSoftMode.class;\n        }\n    }\n\n    private EditText mEditText;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_softmode);\n\n        WindowManager.LayoutParams layoutParams = getWindow().getAttributes();\n        boolean is_state_visible = layoutParams.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;\n\n        TextView textView = findViewById(R.id.result);\n        textView.setText(\"SOFT_INPUT_STATE_VISIBLE:\" + is_state_visible);\n\n        mEditText = findViewById(R.id.edit_view);\n        mEditText.requestFocus();\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/application/TestApplicationActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.application;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.TestApplication;\n\npublic class TestApplicationActivity extends BaseActivity {\n\n    private TextView mText;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_common);\n        mText = findViewById(R.id.text);\n        mText.setText(\"isCallOnCreate:\" + TestApplication.getInstance().isOnCreate);\n    }\n\n    public void doClick(View view) {\n\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/context/ActivityContextSubDirTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.context;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class ActivityContextSubDirTestActivity extends SubDirContextThemeWrapperTestActivity {\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"ActivityContextSubDir测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Activity作为Context因BusinessName不同而隔离的相关特性\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return ActivityContextSubDirTestActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        fillTestValues(this);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/context/ApplicationContextSubDirTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.context;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class ApplicationContextSubDirTestActivity extends SubDirContextThemeWrapperTestActivity {\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"ApplicationContextSubDir测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试Application作为Context因BusinessName不同而隔离的相关特性\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return ApplicationContextSubDirTestActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        fillTestValues(getApplication());\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/context/SubDirContextThemeWrapperTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.context;\n\nimport static android.os.Environment.DIRECTORY_MUSIC;\nimport static android.os.Environment.DIRECTORY_PODCASTS;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.RequiresApi;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.ScrollView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.UiUtil;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FilenameFilter;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\n\nabstract class SubDirContextThemeWrapperTestActivity extends BaseActivity {\n\n    private LinearLayout mRootView;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        LinearLayout linearLayout = new LinearLayout(this);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n\n        ScrollView scrollView = new ScrollView(this);\n        scrollView.addView(linearLayout);\n        setContentView(scrollView);\n        mRootView = linearLayout;\n    }\n\n\n    protected void fillTestValues(Context testContext) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            makeItem(\"getDataDir()\", \"TAG_GET_DATA_DIR\",\n                    testContext.getDataDir().getAbsolutePath()\n            );\n        }\n\n        makeItem(\"getFilesDir()\", \"TAG_GET_FILES_DIR\",\n                testContext.getFilesDir().getAbsolutePath()\n        );\n\n        makeItem(\"openFileInput(\\\"foo\\\")\", \"TAG_OPEN_FILE_INPUT_FOO\",\n                getOpenFileInputAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"openFileInput(\\\"bar\\\")\", \"TAG_OPEN_FILE_INPUT_BAR\",\n                getOpenFileInputAbsolutePath(testContext, \"bar\")\n        );\n\n        makeItem(\"openFileOutput(\\\"foo\\\", MODE_PRIVATE)\", \"TAG_OPEN_FILE_OUTPUT_FOO\",\n                getOpenFileOutputAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"openFileOutput(\\\"bar\\\", MODE_PRIVATE)\", \"TAG_OPEN_FILE_OUTPUT_BAR\",\n                getOpenFileOutputAbsolutePath(testContext, \"bar\")\n        );\n\n        makeItem(\"deleteFile(\\\"foo\\\")\", \"TAG_DELETE_FILE_FOO\",\n                isDeleteFileSuccess(testContext, \"foo\")\n        );\n\n        makeItem(\"deleteFile(\\\"bar\\\")\", \"TAG_DELETE_FILE_BAR\",\n                isDeleteFileSuccess(testContext, \"bar\")\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getNoBackupFilesDir()\", \"TAG_GET_NBF_DIR\",\n                    testContext.getNoBackupFilesDir().getAbsolutePath()\n            );\n        }\n\n        File externalMusicDir = testContext.getExternalFilesDir(DIRECTORY_MUSIC);\n        makeItem(\"getExternalFilesDir(DIRECTORY_MUSIC)\", \"TAG_GET_EFD_MUSIC\",\n                externalMusicDir == null ? \"null\" : externalMusicDir.getAbsolutePath()\n        );\n\n        File externalPodcastsDir = testContext.getExternalFilesDir(DIRECTORY_PODCASTS);\n        makeItem(\"getExternalFilesDir(DIRECTORY_MUSIC)\", \"TAG_GET_EFD_PODCASTS\",\n                externalPodcastsDir == null ? \"null\" : externalPodcastsDir.getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            File[] externalMusicDirs = testContext.getExternalFilesDirs(DIRECTORY_MUSIC);\n            makeItem(\"getExternalFilesDirs(DIRECTORY_MUSIC)\", \"TAG_GET_EFDS_MUSIC\",\n                    Arrays.toString(externalMusicDirs)\n            );\n\n            File[] externalPodcastsDirs = testContext.getExternalFilesDirs(DIRECTORY_PODCASTS);\n            makeItem(\"getExternalFilesDirs(DIRECTORY_MUSIC)\", \"TAG_GET_EFDS_PODCASTS\",\n                    Arrays.toString(externalPodcastsDirs)\n            );\n        }\n\n        makeItem(\"getObbDir()\", \"TAG_GET_OBB_DIR\",\n                testContext.getObbDir().getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            makeItem(\"getObbDirs()\", \"TAG_GET_OBB_DIRS\",\n                    Arrays.toString(testContext.getObbDirs())\n            );\n        }\n\n        makeItem(\"getCacheDir()\", \"TAG_GET_CACHE_DIR\",\n                testContext.getCacheDir().getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getCodeCacheDir()\", \"TAG_GET_CODE_CACHE_DIR\",\n                    testContext.getCodeCacheDir().getAbsolutePath()\n            );\n        }\n\n        File externalCacheDir = testContext.getExternalCacheDir();\n        makeItem(\"getExternalCacheDir()\", \"TAG_GET_EXT_CACHE_DIR\",\n                externalCacheDir == null ? \"null\" : externalCacheDir.getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            makeItem(\"getExternalCacheDirs()\", \"TAG_GET_EXT_CACHE_DIRS\",\n                    Arrays.toString(testContext.getExternalCacheDirs())\n            );\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getExternalMediaDirs()\", \"TAG_GET_EXT_MEDIA_DIRS\",\n                    Arrays.toString(testContext.getExternalMediaDirs())\n            );\n        }\n\n        makeItem(\"getDir(\\\"foo\\\",MODE_PRIVATE)\", \"TAG_GET_DIR_FOO\",\n                testContext.getDir(\"foo\", MODE_PRIVATE).getAbsolutePath()\n        );\n\n        makeItem(\"getDir(\\\"bar\\\",MODE_PRIVATE)\", \"TAG_GET_DIR_BAR\",\n                testContext.getDir(\"bar\", MODE_PRIVATE).getAbsolutePath()\n        );\n\n        makeItem(\"getSharedPreferences(\\\"foo\\\",MODE_PRIVATE)\", \"TAG_GET_SP_FOO\",\n                getSharedPreferencesAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"getSharedPreferences(\\\"bar\\\",MODE_PRIVATE)\", \"TAG_GET_SP_BAR\",\n                getSharedPreferencesAbsolutePath(testContext, \"bar\")\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            makeItem(\"deleteSharedPreferences(\\\"foo\\\")\", \"TAG_DEL_SP_FOO\",\n                    isDeleteSharedPreferencesSuccess(testContext, \"foo\")\n            );\n\n            makeItem(\"deleteSharedPreferences(\\\"bar\\\")\", \"TAG_DEL_SP_BAR\",\n                    isDeleteSharedPreferencesSuccess(testContext, \"bar\")\n            );\n        }\n\n        makeItem(\"openOrCreateDatabase(\\\"foo\\\",MODE_PRIVATE,null)\", \"TAG_OOCD3_FOO\",\n                testContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null).getPath()\n        );\n        testContext.deleteDatabase(\"foo\");\n\n        makeItem(\"openOrCreateDatabase(\\\"bar\\\",MODE_PRIVATE,null)\", \"TAG_OOCD3_BAR\",\n                testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null).getPath()\n        );\n        testContext.deleteDatabase(\"bar\");\n\n        makeItem(\"openOrCreateDatabase(\\\"foo\\\",MODE_PRIVATE,null,null)\", \"TAG_OOCD4_FOO\",\n                testContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null, null).getPath()\n        );\n        testContext.deleteDatabase(\"foo\");\n\n        makeItem(\"openOrCreateDatabase(\\\"bar\\\",MODE_PRIVATE,null,null)\", \"TAG_OOCD4_BAR\",\n                testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null, null).getPath()\n        );\n        testContext.deleteDatabase(\"bar\");\n\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {\n            String value = \"\";\n            try {\n                testContext.moveDatabaseFrom(this, \"foo\");\n            } catch (Exception e) {\n                value = e.getMessage();\n            }\n            makeItem(\"moveDatabaseFrom(this,\\\"foo\\\")\", \"TAG_MOVE_DB_FROM_FOO\",\n                    value\n            );\n\n            try {\n                testContext.moveDatabaseFrom(this, \"bar\");\n            } catch (Exception e) {\n                value = e.getMessage();\n            }\n            makeItem(\"moveDatabaseFrom(this,\\\"bar\\\")\", \"TAG_MOVE_DB_FROM_BAR\",\n                    value\n            );\n        }\n\n        makeItem(\"deleteDatabase(\\\"foo_d\\\")\", \"TAG_DELETE_DB_FOO\",\n                isDeleteDatabaseSuccess(testContext, \"foo_d\")\n        );\n\n        makeItem(\"deleteDatabase(\\\"bar_d\\\")\", \"TAG_DELETE_DB_BAR\",\n                isDeleteDatabaseSuccess(testContext, \"bar_d\")\n        );\n\n        makeItem(\"getDatabasePath(\\\"foo\\\")\", \"TAG_GET_DATABASE_PATH_FOO\",\n                testContext.getDatabasePath(\"foo\").getAbsolutePath()\n        );\n\n        makeItem(\"getDatabasePath(\\\"bar\\\")\", \"TAG_GET_DATABASE_PATH_BAR\",\n                testContext.getDatabasePath(\"bar\").getAbsolutePath()\n        );\n\n\n        Context hostContext = getApplication().getBaseContext();\n        hostContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null);\n        testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null);\n        String[] databaseListArray = testContext.databaseList();\n        List<String> databaseList = new LinkedList<>();\n        Collections.addAll(databaseList, databaseListArray);\n        Iterator<String> iterator = databaseList.iterator();\n        String s;\n        while (iterator.hasNext()) {\n            s = iterator.next();\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {\n                if (s.endsWith(\"-wal\") || s.endsWith(\"-shm\")) {\n                    iterator.remove();\n                }\n            } else {\n                if (s.endsWith(\"-journal\")) {\n                    iterator.remove();\n                }\n            }\n        }\n\n        makeItem(\"databaseList()\", \"TAG_DATABASE_LIST\",\n                Arrays.toString(databaseList.toArray())\n        );\n        hostContext.deleteDatabase(\"foo\");\n        testContext.deleteDatabase(\"bar\");\n    }\n\n    private String getOpenFileInputAbsolutePath(Context context, String name) {\n        String result = \"\";\n        try {\n            context.openFileInput(name);\n        } catch (FileNotFoundException e) {\n            String message = e.getMessage();\n            int i = message.indexOf(name);\n            result = message.substring(0, i + name.length());\n        }\n        return result;\n    }\n\n    private String getOpenFileOutputAbsolutePath(Context context, String name) {\n        File file = new File(context.getFilesDir(), name);\n        if (file.exists()) {\n            throw new RuntimeException(\"测试文件不能提前存在\");\n        }\n        try {\n            FileOutputStream fileOutputStream = context.openFileOutput(name, MODE_PRIVATE);\n            fileOutputStream.write(1);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        if (!file.delete()) {\n            throw new RuntimeException(\"测试文件应该被创建出来了\");\n        }\n        return file.getAbsolutePath();\n    }\n\n    private String isDeleteFileSuccess(Context context, String name) {\n        File foo = new File(context.getFilesDir(), name);\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteFile(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private String getSharedPreferencesAbsolutePath(Context context, final String name) {\n        SharedPreferences sharedPreferences\n                = context.getSharedPreferences(name, MODE_PRIVATE);\n        boolean commit = sharedPreferences.edit().putString(\"test\", \"test\").commit();\n        if (!commit) {\n            throw new RuntimeException(\"commit failed\");\n        }\n\n        Context hostContext = getApplication().getBaseContext();\n        File dataDir = hostContext.getFilesDir().getParentFile();\n        File sharedPrefsDir = new File(dataDir, \"shared_prefs\");\n\n        File[] files = sharedPrefsDir.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File dir, String fileName) {\n                return fileName.contains(name);\n            }\n        });\n        if (files.length != 1) {\n            throw new RuntimeException(\"匹配文件数量不对。\");\n        }\n        String result = files[0].getAbsolutePath();\n\n        if (!files[0].delete()) {\n            throw new RuntimeException(\"删除测试文件失败\");\n        }\n\n        return result;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.N)\n    private String isDeleteSharedPreferencesSuccess(Context context, String name) {\n        File foo = new File(getSharedPreferencesAbsolutePath(context, name));\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteSharedPreferences(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private String isDeleteDatabaseSuccess(Context context, String name) {\n        File foo = context.getDatabasePath(name);\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteDatabase(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private void makeItem(\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        ViewGroup item = UiUtil.makeItem(this, labelText, viewTag, value);\n        mRootView.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/dialog/TestDialog.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.support.annotation.NonNull;\nimport android.view.Window;\n\npublic class TestDialog extends Dialog {\n\n    public TestDialog(@NonNull Context context) {\n        super(context);\n\n        getWindow().requestFeature(Window.FEATURE_NO_TITLE);\n        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/dialog/TestDialogActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.dialog;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestDialogActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"Dialog 相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试show Dialog\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestDialogActivity.class;\n        }\n    }\n\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_dialog_activity);\n    }\n\n    public void show(View view) {\n        TestDialog dialog = new TestDialog(this);\n        dialog.setContentView(R.layout.layout_dialog);\n\n        dialog.show();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/fragment/TestDialogFragment.java",
    "content": "package com.tencent.shadow.sample.plugin.app.lib.usecases.fragment;\n\nimport android.app.Dialog;\nimport android.app.DialogFragment;\nimport android.content.DialogInterface;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\n\npublic class TestDialogFragment extends DialogFragment {\n\n    public static TestDialogFragment newInstance(Bundle bundle) {\n        TestDialogFragment testFragment = new TestDialogFragment();\n        testFragment.setArguments(bundle);\n        return testFragment;\n    }\n\n    @Override\n    public Dialog onCreateDialog(Bundle savedInstanceState) {\n        return super.onCreateDialog(savedInstanceState);\n    }\n\n    @Override\n    public void onDismiss(DialogInterface dialog) {\n        super.onDismiss(dialog);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        Window window = getDialog().getWindow();\n//        window.setWindowAnimations(android.R.style.Animation_Toast);\n        window.setWindowAnimations(R.style.dialog_exit_fade_out);\n\n        View view = inflater.inflate(R.layout.layout_fragment_test, null, false);\n        TextView textView = view.findViewById(R.id.tv_msg);\n        Bundle bundle = getArguments();\n        if (bundle != null) {\n            String msg = bundle.getString(\"msg\");\n            if (!TextUtils.isEmpty(msg)) {\n                textView.setText(msg);\n            }\n        }\n        return view;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/fragment/TestDialogFragmentActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestDialogFragmentActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"DialogFragment相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试DialogFragment使用setWindowAnimations\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestDialogFragmentActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_fragment_activity);\n\n        String msg = \"这是TestDialogFragment\";\n        Bundle bundle = new Bundle();\n        bundle.putString(\"msg\", msg);\n        TestDialogFragment testFragment = TestDialogFragment.newInstance(bundle);\n        testFragment.show(getFragmentManager(), \"TestDialogFragment\");\n\n        getWindow().setWindowAnimations(R.style.dialog_exit_fade_out);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/fragment/TestDynamicFragmentActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestDynamicFragmentActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"代码添加fragment相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试通过代码添加一个fragment\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestDynamicFragmentActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_fragment_activity);\n\n        String msg = \"这是一个动态添加的fragment\";\n        Bundle bundle = new Bundle();\n        bundle.putString(\"msg\", msg);\n        TestFragment testFragment = TestFragment.newInstance(bundle);\n        getFragmentManager().beginTransaction().add(R.id.fragment_container, testFragment).commit();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/fragment/TestFragment.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.fragment;\n\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.text.TextUtils;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\n\npublic class TestFragment extends Fragment {\n\n    public static TestFragment newInstance(Bundle bundle) {\n        TestFragment testFragment = new TestFragment();\n        testFragment.setArguments(bundle);\n        return testFragment;\n    }\n\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.layout_fragment_test, null, false);\n        TextView textView = view.findViewById(R.id.tv_msg);\n        Bundle bundle = getArguments();\n        if (bundle != null) {\n            String msg = bundle.getString(\"msg\");\n            if (!TextUtils.isEmpty(msg)) {\n                textView.setText(msg);\n            }\n        }\n        return view;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/fragment/TestXmlFragmentActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestXmlFragmentActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"xml中使用fragment相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试在Activity现实xml中定义的fragment\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestXmlFragmentActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_fragment_xml_activity);\n\n    }\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/host_communication/PluginUseHostClassActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.host_communication;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.LinearLayout;\n\nimport com.tencent.shadow.sample.host.lib.HostUiLayerProvider;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class PluginUseHostClassActivity extends BaseActivity {\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"插件使用宿主类测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试插件中调用宿主类的方法\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return PluginUseHostClassActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        LinearLayout linearLayout = new LinearLayout(this);\n\n        HostUiLayerProvider hostUiLayerProvider = HostUiLayerProvider.getInstance();\n        View hostUiLayer = hostUiLayerProvider.buildHostUiLayer();\n        linearLayout.addView(hostUiLayer);\n\n        setContentView(linearLayout);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/packagemanager/TestPackageManagerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.packagemanager;\n\nimport android.content.ComponentName;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestPackageManagerActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"PackageManager调用测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试PackageManager相关api的调用，确保插件调用相关api时可以正确获取到插件相关的信息\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestPackageManagerActivity.class;\n        }\n    }\n\n    private TextView mTvTextView;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_packagemanager);\n        mTvTextView = findViewById(R.id.text);\n    }\n\n\n    public void getApplicationInfo(View view) {\n        try {\n            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), 0);\n            mTvTextView.setText(\"ApplicationInfo className:\" + applicationInfo.className +\n                    \"\\nnativeLibraryDir:\" + applicationInfo.nativeLibraryDir\n                    + \"\\nmetaData:\" + applicationInfo.metaData);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    public void getActivityInfo(View view) {\n        try {\n            ActivityInfo activityInfo = getPackageManager().getActivityInfo(new ComponentName(this, this.getClass()), 0);\n            mTvTextView.setText(\"activityInfo name:\" + activityInfo.name\n                    + \"\\npackageName:\" + activityInfo.packageName);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n\n\n    public void getPackageInfo(View view) {\n        try {\n            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);\n            mTvTextView.setText(\"packageInfo versionName:\" + packageInfo.versionName\n                    + \"\\nversionCode:\" + packageInfo.versionCode);\n        } catch (PackageManager.NameNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/provider/TestDBContentProviderActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.provider;\n\nimport android.content.ContentValues;\nimport android.database.ContentObserver;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestDBContentProviderActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"ContentProvider DB相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试通过ContentProvider来操作数据库\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestDBContentProviderActivity.class;\n        }\n    }\n\n    private static final String TAG = \"ContentProviderActivity\";\n\n    private TextView mTextView;\n\n    private Handler mHandler = new Handler();\n    private ContentObserver mObserver = new ContentObserver(mHandler) {\n        @Override\n        public void onChange(boolean selfChange, Uri uri) {\n            super.onChange(selfChange, uri);\n            Log.d(TAG, uri + \" onChange\");\n            Toast.makeText(TestDBContentProviderActivity.this, uri + \" onChange\", Toast.LENGTH_SHORT).show();\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_provider_db);\n\n        mTextView = findViewById(R.id.text);\n\n        getContentResolver().registerContentObserver(TestProviderInfo.TestEntry.CONTENT_URI,\n                false, mObserver);\n    }\n\n    public void insert(View view) {\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis());\n        getContentResolver().insert(TestProviderInfo.TestEntry.CONTENT_URI, contentValues);\n\n        query(view);\n    }\n\n    public void query(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI, null, null, null, null);\n        if (cursor != null) {\n            StringBuilder s = new StringBuilder();\n            while (cursor.moveToNext()) {\n                long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n                String name = cursor.getString(cursor.getColumnIndex(TestProviderInfo.TestEntry.COLUMN_NAME));\n                s.append(\"id:\").append(id).append(\" name:\").append(name).append(\" \\n\");\n            }\n            mTextView.setText(s);\n            cursor.close();\n        } else {\n            Toast.makeText(this, \"请先插入数据\", Toast.LENGTH_SHORT).show();\n        }\n\n    }\n\n    public void update(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI,\n                null, null, null, null);\n        int count = cursor != null ? cursor.getCount() : 0;\n        if (count > 0) {\n            cursor.moveToFirst();\n            ContentValues contentValues = new ContentValues();\n            contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"name \" + System.currentTimeMillis());\n\n            long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n            getContentResolver().update(TestProviderInfo.TestEntry.CONTENT_URI, contentValues,\n                    TestProviderInfo.TestEntry._ID + \" = ?\",\n                    new String[]{String.valueOf(id)});\n        }\n        if (cursor != null) {\n            cursor.close();\n        }\n\n        query(view);\n    }\n\n    public void delete(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI,\n                null, null, null, null);\n        int count = cursor != null ? cursor.getCount() : 0;\n        if (count > 0) {\n            cursor.moveToFirst();\n            long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n            getContentResolver().delete(TestProviderInfo.TestEntry.CONTENT_URI,\n                    TestProviderInfo.TestEntry._ID + \" = ?\",\n                    new String[]{String.valueOf(id)});\n        }\n        if (cursor != null) {\n            cursor.close();\n        }\n\n        query(view);\n    }\n\n    public void bulkInsert(View view) {\n        ContentValues[] values = new ContentValues[3];\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis());\n        values[0] = contentValues;\n\n        contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis() + 5);\n        values[1] = contentValues;\n\n        contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis() + 10);\n        values[2] = contentValues;\n\n        getContentResolver().bulkInsert(TestProviderInfo.TestEntry.CONTENT_URI, values);\n\n        query(view);\n    }\n\n    public void call(View view) {\n        Bundle beauty = getContentResolver().call(TestProviderInfo.TestEntry.CONTENT_URI, \"getBeauty\", \"18\", null);\n        if (beauty != null) {\n            Toast.makeText(this, \"get beauty who name is \" + beauty.getString(\"name\"), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        getContentResolver().unregisterContentObserver(mObserver);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/provider/TestDBHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.provider;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\n\npublic class TestDBHelper extends SQLiteOpenHelper {\n    private static final int DATABASE_VERSION = 1;\n    private static final String DATABASE_NAME = \"shadow.db\";\n\n    public TestDBHelper(Context context) {\n        super(context, DATABASE_NAME, null, DATABASE_VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        final String SQL_CREATE_CONTACT_TABLE = \"CREATE TABLE \" + TestProviderInfo.TestEntry.TABLE_NAME + \"( \"\n                + TestProviderInfo.TestEntry._ID + \" TEXT PRIMARY KEY, \"\n                + TestProviderInfo.TestEntry.COLUMN_NAME + \" TEXT NOT NULL );\";\n\n        db.execSQL(SQL_CREATE_CONTACT_TABLE);\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.execSQL(\"DROP TABLE IF EXISTS \" + TestProviderInfo.TestEntry.TABLE_NAME);\n        onCreate(db);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/provider/TestFileProviderActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.provider;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.MediaStore;\nimport android.support.v4.content.FileProvider;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.View;\nimport android.view.ViewTreeObserver;\nimport android.widget.ImageView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.BuildConfig;\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\nimport java.io.File;\n\n\npublic class TestFileProviderActivity extends Activity {\n    private static final String TAG = \"TestFileProviderActivity\";\n    private static final String KEY_FILE_PATH = \"filePath\";\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"FileProvider相关测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"通过使用系统相机拍照来测试FileProvider\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestFileProviderActivity.class;\n        }\n    }\n\n    private static final int REQUEST_CODE = 1001;\n\n    private ImageView mImageView;\n    private File mFile;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        Log.d(TAG, \"onCreate: \");\n        setContentView(R.layout.activity_test_file_provider);\n        mImageView = findViewById(R.id.photo);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            requestPermissions(new String[]{Manifest.permission.CAMERA}, 1001);\n        }\n\n        findViewById(R.id.go_take_photo).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String fileName = String.valueOf(System.currentTimeMillis());\n                String filePath = getFilesDir() + \"/images/\" + fileName + \".jpg\";\n                mFile = new File(filePath);\n                if (!mFile.getParentFile().exists()) {\n                    mFile.getParentFile().mkdir();\n                }\n\n                Uri contentUri;\n                if (targetSdkVersion() >= Build.VERSION_CODES.N) {\n                    contentUri = FileProvider.getUriForFile(TestFileProviderActivity.this,\n                            BuildConfig.APPLICATION_ID + \".general_cases.fileprovider\", mFile);\n//                    contentUri = Uri.parse(\"content://com.tencent.shadow.contentprovider.authority/com.tencent.shadow.test.plugin.general_cases.lib.gallery.fileprovider\" +\n//                            \"/name/data/data/com.tencent.shadow.test.hostapp/files/images/1548417832706.jpg\");\n                } else {\n                    contentUri = Uri.fromFile(mFile);\n                }\n                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n                intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);\n                startActivityForResult(intent, REQUEST_CODE);\n            }\n        });\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putString(KEY_FILE_PATH, mFile.getAbsolutePath());\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        String filePath = savedInstanceState.getString(KEY_FILE_PATH);\n        if (!TextUtils.isEmpty(filePath)) {\n            mFile = new File(filePath);\n        }\n    }\n\n    private int targetSdkVersion() {\n        return getApplicationContext().getApplicationInfo().targetSdkVersion;\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {\n            setPic();\n        }\n    }\n\n    @SuppressLint(\"LongLogTag\")\n    private void setPic() {\n        if (mFile == null || !mFile.exists()) {\n            Log.w(TAG, \"setPic: file don't exist\");\n            return;\n        }\n\n        mImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {\n            @Override\n            public boolean onPreDraw() {\n                // Get the dimensions of the View\n                int targetW = mImageView.getWidth();\n                int targetH = mImageView.getHeight();\n                mImageView.getViewTreeObserver().removeOnPreDrawListener(this);\n                // Get the dimensions of the bitmap\n                BitmapFactory.Options bmOptions = new BitmapFactory.Options();\n                bmOptions.inJustDecodeBounds = true;\n                BitmapFactory.decodeFile(mFile.getAbsolutePath(), bmOptions);\n                int photoW = bmOptions.outWidth;\n                int photoH = bmOptions.outHeight;\n\n                // Determine how much to scale down the image\n                int scaleFactor = Math.min(photoW / targetW, photoH / targetH);\n\n                // Decode the image file into a Bitmap sized to fill the View\n                bmOptions.inJustDecodeBounds = false;\n                bmOptions.inSampleSize = scaleFactor;\n                bmOptions.inPurgeable = true;\n\n                Bitmap bitmap = BitmapFactory.decodeFile(mFile.getAbsolutePath(), bmOptions);\n                mImageView.setImageBitmap(bitmap);\n                return false;\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/provider/TestProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.provider;\n\nimport android.content.ContentProvider;\nimport android.content.ContentValues;\nimport android.content.UriMatcher;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\n/**\n * TestProvider\n * Created by 90Chris on 2016/5/1.\n */\npublic class TestProvider extends ContentProvider {\n    private TestDBHelper mOpenHelper;\n\n    @Override\n    public boolean onCreate() {\n        mOpenHelper = new TestDBHelper(getContext());\n        return true;\n    }\n\n\n    @Nullable\n    @Override\n    public String getType(Uri uri) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();\n\n        Cursor cursor = null;\n        switch (buildUriMatcher().match(uri)) {\n            case TEST:\n                cursor = db.query(TestProviderInfo.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);\n                break;\n        }\n\n        return cursor;\n    }\n\n    @SuppressWarnings(\"ConstantConditions\")\n    @Nullable\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        Uri returnUri;\n        long _id;\n        switch (buildUriMatcher().match(uri)) {\n            case TEST:\n                _id = db.insert(TestProviderInfo.TestEntry.TABLE_NAME, null, values);\n                if (_id > 0) {\n                    returnUri = TestProviderInfo.TestEntry.buildUri(_id);\n                    getContext().getContentResolver().notifyChange(returnUri, null);\n                } else\n                    throw new android.database.SQLException(\"Failed to insert row into \" + uri);\n                break;\n            default:\n                throw new android.database.SQLException(\"Unknown uri: \" + uri);\n        }\n        return returnUri;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        int result = db.delete(TestProviderInfo.TestEntry.TABLE_NAME, selection, selectionArgs);\n        if (result > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return result;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        int result = db.update(TestProviderInfo.TestEntry.TABLE_NAME, values, selection, selectionArgs);\n        if (result > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return result;\n    }\n\n    public Bundle call(@NonNull String method, String arg, @Nullable Bundle extras) {\n        switch (method) {\n            case \"getBeauty\":\n                Bundle bundle = new Bundle();\n                bundle.putString(\"name\", \"Anne Hathaway\");\n                return bundle;\n        }\n        return null;\n    }\n\n    private final static int TEST = 100;\n\n    static UriMatcher buildUriMatcher() {\n        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);\n        final String authority = TestProviderInfo.CONTENT_AUTHORITY;\n\n        matcher.addURI(authority, TestProviderInfo.PATH_TEST, TEST);\n\n        return matcher;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/provider/TestProviderInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.provider;\n\nimport android.content.ContentUris;\nimport android.net.Uri;\nimport android.provider.BaseColumns;\n\nimport com.tencent.shadow.sample.plugin.app.lib.BuildConfig;\n\n\npublic class TestProviderInfo {\n\n    protected static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + \".provider.test\";\n    protected static final Uri BASE_CONTENT_URI = Uri.parse(\"content://\" + CONTENT_AUTHORITY);\n\n    protected static final String PATH_TEST = \"test\";\n\n    public static final class TestEntry implements BaseColumns {\n\n        public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_TEST).build();\n\n        protected static Uri buildUri(long id) {\n            return ContentUris.withAppendedId(CONTENT_URI, id);\n        }\n\n        protected static final String TABLE_NAME = \"TestProviderInfo\";\n\n        public static final String COLUMN_NAME = \"name\";\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/receiver/MyReceiver.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.receiver;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.widget.Toast;\n\npublic class MyReceiver extends BroadcastReceiver {\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        String msg = intent.getStringExtra(\"msg\");\n        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/receiver/TestDynamicReceiverActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.receiver;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\npublic class TestDynamicReceiverActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"动态广播测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试动态广播的发送和接收是否工作正常\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestDynamicReceiverActivity.class;\n        }\n    }\n\n    private final static String INTENT_ACTION = \"com.tencent.test.action.DYNAMIC\";\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_receiver);\n        Button button = findViewById(R.id.button);\n        button.setText(\"测试动态广播发送\");\n\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(INTENT_ACTION);\n                intent.putExtra(\"msg\", \"收到测试动态广播发送\");\n                sendBroadcast(intent);\n            }\n        });\n\n        DynamicBroadcastReceiver dynamicBroadcastReceiver = new DynamicBroadcastReceiver();\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {\n            registerReceiver(dynamicBroadcastReceiver, new IntentFilter(INTENT_ACTION), Context.RECEIVER_EXPORTED);\n        } else {\n            registerReceiver(dynamicBroadcastReceiver, new IntentFilter(INTENT_ACTION));\n        }\n    }\n\n\n    private class DynamicBroadcastReceiver extends BroadcastReceiver {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String msg = intent.getStringExtra(\"msg\");\n            ToastUtil.showToast(context, msg);\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/receiver/TestReceiverActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.usecases.receiver;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.tencent.shadow.sample.plugin.app.lib.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class TestReceiverActivity extends BaseActivity {\n\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"静态广播测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试静态广播的发送和接收是否工作正常\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return TestReceiverActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_receiver);\n        Button button = findViewById(R.id.button);\n        button.setText(\"测试静态广播发送\");\n\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(\"com.tencent.test.action\");\n                intent.putExtra(\"msg\", \"收到测试静态广播发送\");\n                sendBroadcast(intent);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/service/HostAddPluginViewService.java",
    "content": "package com.tencent.shadow.sample.plugin.app.lib.usecases.service;\n\nimport android.app.IntentService;\nimport android.content.Intent;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.support.annotation.Nullable;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport com.tencent.shadow.sample.host.lib.HostAddPluginViewContainer;\nimport com.tencent.shadow.sample.host.lib.HostAddPluginViewContainerHolder;\nimport com.tencent.shadow.sample.plugin.app.lib.R;\n\npublic class HostAddPluginViewService extends IntentService {\n    private final Handler uiHandler = new Handler(Looper.getMainLooper());\n\n    public HostAddPluginViewService() {\n        super(\"HostAddPluginViewService\");\n    }\n\n    @Override\n    protected void onHandleIntent(@Nullable Intent intent) {\n        int id = intent.getIntExtra(\"id\", 0);\n        HostAddPluginViewContainer viewContainer\n                = HostAddPluginViewContainerHolder.instances.remove(id);\n\n        uiHandler.post(() -> {\n            View view = LayoutInflater.from(this).inflate(\n                    R.layout.layout_host_add_plugin_view, null, false);\n            viewContainer.addView(view);\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/java/com/tencent/shadow/sample/plugin/app/lib/usecases/webview/WebViewActivity.java",
    "content": "package com.tencent.shadow.sample.plugin.app.lib.usecases.webview;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\n\npublic class WebViewActivity extends Activity {\n    public static class Case extends UseCase {\n        @Override\n        public String getName() {\n            return \"WebView测试\";\n        }\n\n        @Override\n        public String getSummary() {\n            return \"测试WebView是否能正常工作\";\n        }\n\n        @Override\n        public Class getPageClass() {\n            return WebViewActivity.class;\n        }\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        WebView webView = new FooWebView(this);\n        webView.getSettings().setJavaScriptEnabled(true);\n        webView.loadUrl(\"file:///android_asset/web/test.html?t=\" + Math.random());\n\n        setContentView(webView);\n    }\n}\n\n/**\n * 复现\n * https://github.com/Tencent/Shadow/issues/1175\n */\nclass FooWebView extends WebView {\n\n    public FooWebView(@NonNull Context context) {\n        super(context);\n    }\n\n    @Override\n    public void setWebViewClient(@NonNull WebViewClient client) {\n        FooWebViewClient fooWebViewClient = (FooWebViewClient) client;\n        super.setWebViewClient(fooWebViewClient);\n    }\n}\n\nclass FooWebViewClient extends WebViewClient {\n\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/anim/dialog_exit_fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <scale\n        android:duration=\"50000\"\n        android:fromXScale=\"100%\"\n        android:fromYScale=\"100%\"\n        android:toXScale=\"0%\"\n        android:toYScale=\"0%\" />\n</set>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/drawable/selector_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@android:color/darker_gray\" android:state_pressed=\"true\"></item>\n    <item android:drawable=\"@android:color/darker_gray\" android:state_selected=\"true\"></item>\n    <item android:drawable=\"@android:color/holo_green_dark\"></item>\n\n</selector>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/drawable/selector_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@android:color/darker_gray\" android:state_pressed=\"true\"></item>\n    <item android:drawable=\"@android:color/darker_gray\" android:state_selected=\"true\"></item>\n\n</selector>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/activity_test_file_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestFileProviderActivity\">\n\n    <Button\n        android:id=\"@+id/go_take_photo\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"\n        android:text=\"去拍照\"\n        android:textSize=\"20dp\" />\n\n    <ImageView\n        android:id=\"@+id/photo\"\n        android:layout_width=\"180dp\"\n        android:layout_height=\"320dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"@android:color/darker_gray\"\n        android:scaleType=\"centerInside\" />\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/activity_test_re_create_by_system.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreateBySystem\">\n\n    <TextView\n        android:id=\"@+id/url_tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"18dp\" />\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_activity_lifecycle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:tag=\"tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"Activity生命周期测试\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_activity_settheme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"10dp\"\n        android:layout_height=\"wrap_content\">\n\n        <Button\n            android:id=\"@+id/button\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"点我改变主题\" />\n\n        <Switch\n            android:id=\"@+id/switch1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"Switch\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_margin=\"10dp\"\n        android:orientation=\"horizontal\"\n        android:layout_height=\"wrap_content\">\n\n        <Switch\n            android:id=\"@+id/switch2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"Switch\" />\n\n        <ProgressBar\n            android:id=\"@+id/progressBar\"\n            style=\"?android:attr/progressBarStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"10dp\"\n        android:layout_height=\"wrap_content\">\n\n        <ProgressBar\n            android:id=\"@+id/progress_bar2\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"10dp\"\n        android:layout_height=\"wrap_content\">\n\n        <SeekBar\n            android:id=\"@+id/seek_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:max=\"100\"\n            android:progress=\"50\" />\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"10dp\"\n        android:layout_height=\"wrap_content\">\n\n        <CheckBox\n            android:id=\"@+id/checkBox\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:checked=\"true\"\n            android:text=\"CheckBox\" />\n\n        <CheckBox\n            android:id=\"@+id/checkBox2\"\n            android:checked=\"false\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"CheckBox\" />\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_common.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_gravity=\"center\"\n        android:textColor=\"@android:color/black\"\n        android:text=\"Test\"\n        android:tag=\"text\" />\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:tag=\"button\"\n        android:textColor=\"@android:color/background_dark\"\n        android:textSize=\"22sp\"\n        android:onClick=\"doClick\"\n        android:text=\"click\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"300dp\"\n    android:layout_height=\"200dp\"\n    android:background=\"@android:color/darker_gray\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:textColor=\"@android:color/white\"\n        android:text=\"dialog Test\" />\n\n\n    <TextView\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"22sp\"\n        android:text=\"这是一个dialog\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_dialog_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"show\"\n        android:text=\"显示一个dialog\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    android:orientation=\"horizontal\">\n\n\n    <FrameLayout\n        android:id=\"@+id/fragment_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_fragment_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"22sp\"\n        android:text=\"这是一个fragment\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_fragment_xml_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <fragment\n        android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestFragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_host_add_plugin_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/host_add_plugin_view\" />\n\n    <ImageView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/collapse\" />\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_orientation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"setOrientation\"\n        android:text=\"改变屏幕横竖屏状态\"\n        android:layout_gravity=\"top\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_packagemanager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"20dp\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getApplicationInfo\"\n        android:text=\"getApplicationInfo\"\n        android:layout_gravity=\"top\" />\n\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getActivityInfo\"\n        android:text=\"getActivityInfo\"\n        android:layout_gravity=\"top\" />\n\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getPackageInfo\"\n        android:text=\"getPackageInfo\" />\n\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/white\"\n\n        />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_provider_db.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"20dp\"\n    android:orientation=\"vertical\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:id=\"@+id/button\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"insert\"\n            android:text=\"插入数据\" />\n\n        <Button\n            android:id=\"@+id/button1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"query\"\n            android:text=\"读取数据\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"update\"\n            android:text=\"更新数据\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"20dp\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"delete\"\n            android:text=\"删除数据\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"bulkInsert\"\n            android:text=\"批量插入\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"call\"\n            android:text=\"call\" />\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/white\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_receiver.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:text=\"测试静态广播发送\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_recreate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"reCreate\"\n        android:text=\"reCreate\"\n        android:layout_gravity=\"top\" />\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"Result:\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_result.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:gravity=\"center\">\n\n    <TextView\n        android:id=\"@+id/result\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"18sp\"\n        android:textColor=\"@android:color/black\"\n        android:padding=\"20dp\"\n        android:layout_gravity=\"center\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_service.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_marginTop=\"16dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:tag=\"start\"\n            android:onClick=\"start\"\n            android:text=\"startService\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:tag=\"bind\"\n            android:onClick=\"bind\"\n            android:text=\"bindService\" />\n\n    </LinearLayout>\n\n\n    <LinearLayout\n        android:layout_marginTop=\"16dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:onClick=\"stop\"\n            android:tag=\"stop\"\n            android:text=\"stopService\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:onClick=\"unbind\"\n            android:tag=\"unbind\"\n            android:text=\"unbindService\" />\n\n\n    </LinearLayout>\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"testBinder\"\n        android:tag=\"testBinder\"\n        android:text=\"testBinder调用\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"30dp\"\n        android:tag=\"text\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_softmode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:layout_gravity=\"center\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"18sp\"\n            android:textColor=\"@android:color/black\"\n            android:padding=\"20dp\"\n            android:text=\"AndroidManifest中设置了windowSoftInputMode属性为stateVisible,输入法应该自动弹出\" />\n\n\n        <TextView\n            android:id=\"@+id/result\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"18sp\"\n            android:textColor=\"@android:color/black\"\n            android:padding=\"20dp\" />\n\n    </LinearLayout>\n\n\n    <EditText\n        android:id=\"@+id/edit_view\"\n        android:layout_gravity=\"bottom\"\n        android:hint=\"请输入\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"40dp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/layout/layout_test_view_cons_cache.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"没有Crash就是正常的\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n    <com.tencent.shadow.test.lib.custom_view.TestViewConstructorCacheView\n        android:id=\"@+id/testView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/menu/case_test_activity_option_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/icon1\"\n        android:icon=\"@drawable/collapse\"\n        android:showAsAction=\"ifRoom\"\n        android:title=\"标题一\" />\n\n    <item\n        android:id=\"@+id/icon2\"\n        android:showAsAction=\"never\"\n        android:title=\"标题一\" />\n\n    <item\n        android:id=\"@+id/icon3\"\n        android:icon=\"@drawable/collapse\"\n        android:showAsAction=\"never\"\n        android:title=\"标题一\" />\n</menu>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <!-- Simple strings. -->\n    <string name=\"app_name\">Shadow主测试用例集合</string>\n    <string name=\"host_add_plugin_view\">这是插件中的string资源</string>\n</resources>\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"activity_exit_fade_out\" parent=\"android:Animation\" mce_bogus=\"1\">\n        <item name=\"android:activityCloseExitAnimation\">@anim/dialog_exit_fade_out</item>\n    </style>\n\n    <style name=\"dialog_exit_fade_out\" parent=\"android:Animation\" mce_bogus=\"1\">\n        <item name=\"android:windowExitAnimation\">@anim/dialog_exit_fade_out</item>\n    </style>\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"TestPluginTheme\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\">\n        <!--插件设置的android:windowIsTranslucent不能生效,宿主中注册的壳子Activity必须设置windowIsTranslucent==true-->\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:background\">#64f56701</item>\n    </style>\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-app/src/main/res/values-v21/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"PluginAppThemeLight\" parent=\"@android:style/Theme.Material.Light\">\n        <item name=\"android:colorPrimary\">@color/primary_color_purple</item>\n        <item name=\"android:colorPrimaryDark\">@color/primary_color_purple_dark</item>\n        <item name=\"android:colorAccent\">@color/accent_color_purple</item>\n        <item name=\"android:navigationBarColor\">@color/primary_color_purple_dark</item>\n        <item name=\"android:colorButtonNormal\">@color/accent_color_purple</item>\n    </style>\n\n    <color name=\"primary_color_purple\">#9c27b0</color>\n    <color name=\"primary_color_purple_dark\">#7b1fa2</color>\n    <color name=\"accent_color_purple\">#e040fb</color>\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base/build.gradle",
    "content": "buildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n\n    dependencies {\n        classpath 'com.tencent.shadow.core:runtime'\n        classpath 'com.tencent.shadow.core:activity-container'\n        classpath 'com.tencent.shadow.core:gradle-plugin'\n        classpath \"org.javassist:javassist:$javassist_version\"\n    }\n}\n\napply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId 'com.tencent.shadow.sample.plugin.lib.base'\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        }\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation project(\":sample-base-lib\")\n\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly 'com.tencent.shadow.core:runtime'\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n#这是Shadow在编译期将AndroidManifest.xml中所需信息生成的Java类，没有被代码自然引用，所以需要手工keep住。\n-keep class com.tencent.shadow.core.manifest_parser.PluginManifest{*;}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.plugin.app.lib.base.plugin\">\n\n    <application\n        android:name=\"com.tencent.shadow.sample.plugin.app.lib.gallery.TestApplication\"\n        android:theme=\"@style/CustomActivityTheme\" />\n</manifest>\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            consumerProguardFiles 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation project(\":slidingmenu\")\n    implementation project(\":pinnedheaderexpandablelistview\")\n    implementation \"com.android.support:support-annotations:$android_support_annotations_version\"\n    api \"com.android.support:support-v4:$android_support_version\"\n    api \"com.android.support:appcompat-v7:$android_support_version\"\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# 这是供sample-app模块依赖的类\n-keep class com.tencent.shadow.sample.plugin.app.lib.gallery.cases.UseCaseManager{*;}\n-keep class com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.*{*;}\n-keep class com.tencent.shadow.sample.plugin.app.lib.gallery.BaseActivity{*;}\n-keep class com.tencent.shadow.sample.plugin.app.lib.gallery.util.*{*;}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.sample.plugin.app.lib.base\">\n\n    <application\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:label=\"@string/app_name\">\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity\"\n            android:theme=\"@style/CustomActivityTheme\"\n            android:configChanges=\"orientation|keyboardHidden\"\n            android:launchMode=\"singleTask\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.tencent.shadow.sample.plugin.app.lib.gallery.MainActivity\" />\n\n        <provider\n            android:name=\"android.support.v4.content.FileProvider\"\n            android:authorities=\"${applicationId}.general_cases.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/filepaths\" />\n        </provider>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/BaseActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\npublic class BaseActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/MainActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery;\n\nimport android.app.Activity;\nimport android.app.FragmentTransaction;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.SparseBooleanArray;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView.LayoutParams;\nimport android.widget.BaseExpandableListAdapter;\nimport android.widget.ExpandableListView;\nimport android.widget.TextView;\n\nimport com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;\nimport com.ryg.expandable.ui.PinnedHeaderExpandableListView;\nimport com.ryg.expandable.ui.PinnedHeaderExpandableListView.OnHeaderUpdateListener;\nimport com.ryg.expandable.ui.StickyLayout;\nimport com.ryg.expandable.ui.StickyLayout.OnGiveUpTouchEventListener;\nimport com.tencent.shadow.sample.plugin.app.lib.base.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.UseCaseManager;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.UseCaseSummaryFragment;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCaseCategory;\n\nimport java.util.List;\n\npublic class MainActivity extends Activity implements\n        ExpandableListView.OnChildClickListener,\n        ExpandableListView.OnGroupClickListener,\n        OnHeaderUpdateListener, OnGiveUpTouchEventListener {\n\n    private PinnedHeaderExpandableListView expandableListView;\n    private StickyLayout stickyLayout;\n    private List<UseCaseCategory> categoryList;\n    private SparseBooleanArray expandStatus;\n    private SlidingMenu slidingMenu;\n\n    private ExpandableListAdapter adapter;\n    private UseCaseSummaryFragment caseSummaryFragment;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_main);\n        expandableListView = findViewById(R.id.expandablelist);\n        stickyLayout = findViewById(R.id.sticky_layout);\n        slidingMenu = findViewById(R.id.slidingmenu);\n\n        caseSummaryFragment = new UseCaseSummaryFragment();\n        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();\n        fragmentTransaction.add(R.id.fragment_container, caseSummaryFragment, \"CaseSummaryFragment\");\n        fragmentTransaction.commitAllowingStateLoss();\n\n        categoryList = UseCaseManager.useCases;\n        expandStatus = new SparseBooleanArray();\n\n        adapter = new ExpandableListAdapter(this);\n        expandableListView.setAdapter(adapter);\n\n        expandableListView.setOnHeaderUpdateListener(this);\n        expandableListView.setOnChildClickListener(this);\n        expandableListView.setOnGroupClickListener(this);\n\n        stickyLayout.setOnGiveUpTouchEventListener(this);\n        slidingMenu.showMenu();\n\n    }\n\n\n    class ExpandableListAdapter extends BaseExpandableListAdapter {\n        private LayoutInflater inflater;\n\n        public ExpandableListAdapter(Context context) {\n            inflater = LayoutInflater.from(context);\n        }\n\n        // 返回父列表个数\n        @Override\n        public int getGroupCount() {\n            return categoryList.size();\n        }\n\n        // 返回子列表个数\n        @Override\n        public int getChildrenCount(int groupPosition) {\n            return categoryList.get(groupPosition).caseList.length;\n        }\n\n        @Override\n        public Object getGroup(int groupPosition) {\n            if (groupPosition >= 0)\n                return categoryList.get(groupPosition);\n            else return null;\n        }\n\n        @Override\n        public Object getChild(int groupPosition, int childPosition) {\n            return categoryList.get(groupPosition).caseList[childPosition];\n        }\n\n        @Override\n        public long getGroupId(int groupPosition) {\n            return groupPosition;\n        }\n\n        @Override\n        public long getChildId(int groupPosition, int childPosition) {\n            return childPosition;\n        }\n\n        @Override\n        public boolean hasStableIds() {\n\n            return true;\n        }\n\n        @Override\n        public View getGroupView(int groupPosition, boolean isExpanded,\n                                 View convertView, ViewGroup parent) {\n            CaseCategoryHolder groupHolder = null;\n            if (convertView == null) {\n                groupHolder = new CaseCategoryHolder();\n                convertView = inflater.inflate(R.layout.layout_case_category_item, null);\n                groupHolder.textCategory = (TextView) convertView\n                        .findViewById(R.id.tv_category);\n                convertView.setTag(groupHolder);\n            } else {\n                groupHolder = (CaseCategoryHolder) convertView.getTag();\n            }\n            expandStatus.put(groupPosition, isExpanded);\n            String title = ((UseCaseCategory) getGroup(groupPosition)).title;\n            groupHolder.textCategory.setText(isExpanded ? title + \" - \" : title + \" + \");\n            return convertView;\n        }\n\n        @Override\n        public View getChildView(int groupPosition, int childPosition,\n                                 boolean isLastChild, View convertView, ViewGroup parent) {\n            CaseItemHolder childHolder = null;\n            if (convertView == null) {\n                childHolder = new CaseItemHolder();\n                convertView = inflater.inflate(R.layout.layout_case_item, null);\n\n                childHolder.textName = (TextView) convertView\n                        .findViewById(R.id.tv_case);\n                convertView.setTag(childHolder);\n            } else {\n                childHolder = (CaseItemHolder) convertView.getTag();\n            }\n\n            childHolder.textName.setText(((UseCase) getChild(groupPosition,\n                    childPosition)).getName());\n            return convertView;\n        }\n\n        @Override\n        public boolean isChildSelectable(int groupPosition, int childPosition) {\n            return true;\n        }\n    }\n\n    @Override\n    public boolean onGroupClick(final ExpandableListView parent, final View v,\n                                int groupPosition, final long id) {\n        return false;\n    }\n\n    @Override\n    public boolean onChildClick(ExpandableListView parent, View v,\n                                int groupPosition, int childPosition, long id) {\n        UseCase useCase = categoryList.get(groupPosition).caseList[childPosition];\n        caseSummaryFragment.setCase(useCase);\n\n        slidingMenu.showMenu();\n        return false;\n    }\n\n    class CaseCategoryHolder {\n        TextView textCategory;\n    }\n\n    class CaseItemHolder {\n        TextView textName;\n    }\n\n    @Override\n    public View getPinnedHeader() {\n        View headerView = (ViewGroup) getLayoutInflater().inflate(R.layout.layout_case_category_item, null);\n        headerView.setLayoutParams(new LayoutParams(\n                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));\n\n        return headerView;\n    }\n\n    @Override\n    public void updatePinnedHeader(View headerView, int firstVisibleGroupPos) {\n        UseCaseCategory firstVisibleGroup = (UseCaseCategory) adapter.getGroup(firstVisibleGroupPos);\n        if (firstVisibleGroup == null) return;\n\n        TextView textView = headerView.findViewById(R.id.tv_category);\n        String title = firstVisibleGroup.title;\n        textView.setText(expandStatus.get(firstVisibleGroupPos) ? title + \" - \" : title + \" + \");\n    }\n\n    @Override\n    public boolean giveUpTouchEvent(MotionEvent event) {\n        if (expandableListView.getFirstVisiblePosition() == 0) {\n            View view = expandableListView.getChildAt(0);\n            if (view != null && view.getTop() >= 0) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/TestApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery;\n\nimport android.app.Application;\n\npublic class TestApplication extends Application {\n\n    private static TestApplication sInstence;\n\n    public boolean isOnCreate;\n\n    @Override\n    public void onCreate() {\n        sInstence = this;\n        isOnCreate = true;\n        super.onCreate();\n    }\n\n    public static TestApplication getInstance() {\n        return sInstence;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/cases/UseCaseManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.cases;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCaseCategory;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class UseCaseManager {\n\n    public static List<UseCaseCategory> useCases = new ArrayList<>();\n\n    public static boolean sInit;\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/cases/UseCaseSummaryFragment.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.cases;\n\nimport android.app.ActivityOptions;\nimport android.app.Fragment;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.sample.plugin.app.lib.base.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity.UseCase;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.PluginChecker;\n\npublic class UseCaseSummaryFragment extends Fragment {\n\n    private TextView mCaseName;\n    private Button mStartCase;\n    private TextView mCaseSummary;\n    private TextView mEnvironment;\n\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.layout_fragment_case_summary, container, false);\n        bindViews(view);\n        return view;\n    }\n\n\n    public void setCase(final UseCase useCase) {\n        mCaseName.setText(useCase.getName());\n        mCaseSummary.setText(useCase.getSummary());\n        mStartCase.setVisibility(View.VISIBLE);\n\n        mStartCase.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Intent intent = new Intent(getActivity(), useCase.getPageClass());\n                if (useCase.getPageParams() != null) {\n                    intent.putExtras(useCase.getPageParams());\n                }\n\n                //只在API 21以上手工测试一下ActivityOptions\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                    ActivityOptions activityOptions = ActivityOptions.makeCustomAnimation(\n                            getActivity(),\n                            android.R.anim.slide_in_left,\n                            android.R.anim.slide_out_right\n                    );\n\n                    //测试调用makeSceneTransitionAnimation方法传入Activity\n\n                    ActivityOptions.makeSceneTransitionAnimation(\n                            getActivity(),\n                            UseCaseSummaryFragment.this.mCaseName,\n                            \"mCaseName\"\n                    );\n\n                    //测试调用makeSceneTransitionAnimation方法传入Activity\n                    ActivityOptions.makeSceneTransitionAnimation(\n                            getActivity(),\n                            new Pair<>(UseCaseSummaryFragment.this.mCaseName, \"mCaseName\")\n                    );\n\n                    startActivity(intent,\n                            activityOptions.toBundle()\n                    );\n                } else {\n                    startActivity(intent);\n                }\n            }\n        });\n    }\n\n\n    private void bindViews(View view) {\n        mCaseName = (TextView) view.findViewById(R.id.case_name);\n        mStartCase = (Button) view.findViewById(R.id.start_case);\n        mCaseSummary = (TextView) view.findViewById(R.id.case_summary);\n        mEnvironment = (TextView) view.findViewById(R.id.environment);\n\n        mEnvironment.setText(PluginChecker.isPluginMode() ? \"当前环境：插件模式\" : \"当前环境：独立安装\");\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/cases/entity/UseCase.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity;\n\nimport android.os.Bundle;\n\npublic abstract class UseCase {\n\n    public abstract String getName();\n\n    public abstract String getSummary();\n\n    public abstract Class getPageClass();\n\n    public Bundle getPageParams() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/cases/entity/UseCaseCategory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.cases.entity;\n\npublic class UseCaseCategory {\n\n    public String title;\n\n    public UseCase[] caseList;\n\n    public UseCaseCategory(String title, UseCase[] caseList) {\n        this.title = title;\n        this.caseList = caseList;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/splash/ISplashAnimation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.splash;\n\npublic interface ISplashAnimation {\n\n    void start();\n\n    void stop();\n\n    void setAnimationListener(AnimationListener animationListener);\n\n\n    interface AnimationListener {\n        void onAnimationEnd();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/splash/SplashActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.splash;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.sample.plugin.app.lib.base.R;\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.MainActivity;\n\npublic class SplashActivity extends Activity {\n\n    private SplashAnimation mSplashAnimation;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_splash);\n\n        mSplashAnimation = new SplashAnimation(this);\n        mSplashAnimation.start();\n\n        mSplashAnimation.setAnimationListener(new ISplashAnimation.AnimationListener() {\n            @Override\n            public void onAnimationEnd() {\n                finish();\n\n                startActivity(new Intent(SplashActivity.this, MainActivity.class));\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/splash/SplashAnimation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.splash;\n\nimport android.content.Context;\nimport android.os.Handler;\n\nimport com.tencent.shadow.sample.plugin.app.lib.gallery.util.ToastUtil;\n\npublic class SplashAnimation implements ISplashAnimation {\n\n    private AnimationListener mAnimationListener;\n\n    private Context mContext;\n\n    public SplashAnimation(Context context) {\n        mContext = context;\n    }\n\n\n    @Override\n    public void start() {\n        ToastUtil.showToast(mContext, \"animation start\");\n\n        new Handler().postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                if (mAnimationListener != null) {\n                    mAnimationListener.onAnimationEnd();\n                }\n            }\n        }, 2000);\n    }\n\n    @Override\n    public void stop() {\n\n    }\n\n    @Override\n    public void setAnimationListener(AnimationListener animationListener) {\n        mAnimationListener = animationListener;\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/util/PluginChecker.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.util;\n\n\npublic class PluginChecker {\n\n    private static Boolean sPluginMode;\n\n    /**\n     * 检测当前是否处于插件状态下\n     * 这里先简单通过访问一个插件框架中的类是否成功来判断\n     *\n     * @return true 是插件模式\n     */\n    public static boolean isPluginMode() {\n        if (sPluginMode == null) {\n            try {\n                PluginChecker.class.getClassLoader().loadClass(\"com.tencent.shadow.core.runtime.ShadowApplication\");\n                sPluginMode = true;\n            } catch (ClassNotFoundException e) {\n                sPluginMode = false;\n            }\n        }\n        return sPluginMode;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/util/ToastUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.util;\n\nimport android.content.Context;\nimport android.widget.Toast;\n\npublic class ToastUtil {\n\n    public static void showToast(Context context, String message) {\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/java/com/tencent/shadow/sample/plugin/app/lib/gallery/util/UiUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.app.lib.gallery.util;\n\nimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nfinal public class UiUtil {\n    @SuppressLint(\"SetTextI18n\")\n    public static ViewGroup makeItemView(Context viewContext, String labelText, String viewTag) {\n        TextView label = new TextView(viewContext);\n        label.setText(labelText + \":\");\n        label.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n\n        TextView value = new TextView(viewContext);\n        value.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n        value.setTag(viewTag);\n\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        linearLayout.setPadding(0, 10, 0, 10);\n\n        linearLayout.addView(label);\n        linearLayout.addView(value);\n        return linearLayout;\n    }\n\n    public static void setItemValue(ViewGroup viewGroupContainsItem, String viewTag, String value) {\n        TextView textView = viewGroupContainsItem.findViewWithTag(viewTag);\n        textView.setText(value);\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        setItemValue(itemView, viewTag, value);\n        return itemView;\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            AsyncGetValue asyncGetValue\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        asyncGetValue.getValue(new AsyncGetValueCallback() {\n            @Override\n            public void onGotValue(String value) {\n                setItemValue(itemView, viewTag, value);\n            }\n        });\n        return itemView;\n    }\n\n    interface AsyncGetValue {\n        void getValue(AsyncGetValueCallback callback);\n    }\n\n    interface AsyncGetValueCallback {\n        void onGotValue(String value);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/drawable/child_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item>\n        <shape>\n            <gradient\n                android:angle=\"0\"\n                android:centerColor=\"#CCCCCC\"\n                android:endColor=\"#FFFFFF\"\n                android:height=\"1px\"\n                android:startColor=\"#FFFFFF\" />\n        </shape>\n    </item>\n\n</layer-list>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_case_category_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:background=\"@android:color/holo_blue_light\"\n    android:layout_height=\"100dp\">\n\n    <TextView\n        android:id=\"@+id/tv_category\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:textColor=\"@android:color/black\"\n        android:layout_gravity=\"center_vertical\"\n        android:textSize=\"22sp\" />\n\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_case_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:background=\"@android:color/holo_blue_dark\"\n    android:layout_height=\"60dp\">\n\n    <TextView\n        android:id=\"@+id/tv_case\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"40dp\"\n        android:textColor=\"@android:color/black\"\n        android:layout_gravity=\"center_vertical\"\n        android:textSize=\"20sp\" />\n\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_fragment_case_summary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/holo_green_light\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"30dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/case_name\"\n            android:layout_width=\"200dp\"\n            android:layout_height=\"60dp\"\n            android:background=\"@android:color/white\"\n            android:gravity=\"left|center_vertical\"\n            android:paddingLeft=\"6dp\"\n            android:textColor=\"@android:color/black\"\n            android:textSize=\"16sp\"\n            android:text=\"Shadow主测试用例集合\" />\n\n        <Button\n            android:id=\"@+id/start_case\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"40dp\"\n            android:layout_marginLeft=\"40dp\"\n            android:background=\"@android:color/holo_orange_dark\"\n            android:visibility=\"gone\"\n            android:textColor=\"@android:color/white\"\n            tools:visibility=\"visible\"\n            android:text=\"启动\" />\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/case_summary\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@android:color/white\"\n        android:gravity=\"left\"\n        android:padding=\"10dp\"\n        android:text=\"这是一个插件框架的测试用例，您可以测试代码在独立安装的环境和插件的环境运行的情况。\n           \\n\\n左滑开始选择测试用例开始测试。\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"16sp\" />\n\n    <TextView\n        android:id=\"@+id/environment\"\n        android:layout_width=\"160dp\"\n        android:layout_height=\"40dp\"\n        android:layout_gravity=\"right\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_marginBottom=\"20dp\"\n        android:background=\"@android:color/holo_red_light\"\n        android:gravity=\"left|center_vertical\"\n        android:paddingLeft=\"6dp\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"16sp\"\n        tools:text=\"测试用例\" />\n\n</LinearLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_main.xml",
    "content": "<!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<com.jeremyfeinstein.slidingmenu.lib.SlidingMenu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:sliding=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/slidingmenu\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    sliding:viewAbove=\"@layout/layout_main_above\"\n    sliding:mode=\"right\"\n    sliding:fadeEnabled=\"false\"\n    sliding:viewBehind=\"@layout/layout_main_behind\"\n    sliding:touchModeAbove=\"fullscreen\"\n    sliding:behindOffset=\"@dimen/behindOffset\">\n\n</com.jeremyfeinstein.slidingmenu.lib.SlidingMenu>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_main_above.xml",
    "content": "<!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<com.ryg.expandable.ui.StickyLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/sticky_layout\"\n    android:layout_width=\"0dp\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginTop=\"0dp\"\n    android:layout_weight=\"1\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:id=\"@+id/sticky_header\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1px\"\n        android:gravity=\"center\"\n        android:visibility=\"gone\"\n        android:orientation=\"vertical\" />\n\n    <LinearLayout\n        android:id=\"@+id/sticky_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <com.ryg.expandable.ui.PinnedHeaderExpandableListView\n            android:id=\"@+id/expandablelist\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:cacheColorHint=\"@null\"\n            android:childDivider=\"@drawable/child_bg\"\n            android:childIndicatorLeft=\"0dp\"\n            android:divider=\"@android:color/darker_gray\"\n            android:dividerHeight=\"1dp\"\n            android:groupIndicator=\"@null\"\n            android:scrollbarAlwaysDrawHorizontalTrack=\"false\" />\n    </LinearLayout>\n\n</com.ryg.expandable.ui.StickyLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_main_behind.xml",
    "content": "<!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/layout/layout_splash.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Splash \"\n        android:textColor=\"@android:color/black\"\n        android:layout_gravity=\"center\" />\n\n</FrameLayout>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <dimen name=\"behindOffset\">40dp</dimen>>\n\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <!-- Simple strings. -->\n    <string name=\"app_name\">sample-base-lib</string>\n</resources>\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"CustomActivityTheme\" parent=\"@android:style/Theme.NoTitleBar\"></style>\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-base-lib/src/main/res/xml/filepaths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <root-path\n        name=\"name\"\n        path=\"\" />\n\n    <file-path\n        name=\"files\"\n        path=\"/\" />\n\n    <cache-path\n        name=\"cache\"\n        path=\"/\" />\n\n    <external-files-path\n        name=\"external\"\n        path=\"/\" />\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.core:loader'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader-impl'\n    implementation project(':sample-constant')\n\n    compileOnly 'com.tencent.shadow.core:runtime'\n    compileOnly 'com.tencent.shadow.core:activity-container'\n    compileOnly 'com.tencent.shadow.core:common'\n    //下面这行依赖是为了防止在proguard的时候找不到LoaderFactory接口\n    compileOnly 'com.tencent.shadow.dynamic:dynamic-host'\n\n    compileOnly files(\"${project(\":sample-host-lib\").getBuildDir()}/outputs/jar/sample-host-lib-debug.jar\")\n}\n\npreBuild.dependsOn(\":sample-host-lib:jarDebugPackage\")\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n\n#kotlin一般性配置 START\n-dontwarn kotlin.**\n-keepclassmembers class **$WhenMappings {\n    <fields>;\n}\n-keepclassmembers class kotlin.Metadata {\n    public <methods>;\n}\n#kotlin一般性配置 END\n\n#kotlin优化性能 START\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);\n}\n#kotlin优化性能 END\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.loader.**{*;}\n-keep class com.tencent.shadow.core.runtime.**{*;}\n\n-dontwarn  com.tencent.shadow.dynamic.host.**\n-dontwarn  com.tencent.shadow.dynamic.impl.**\n-dontwarn  com.tencent.shadow.dynamic.loader.**\n-dontwarn  com.tencent.shadow.core.common.**\n-dontwarn  com.tencent.shadow.core.loader.**\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.plugin.loader\" />\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\n/**\n * 此类包名及类名固定\n * classLoader的白名单\n * PluginLoader可以加载宿主中位于白名单内的类\n */\npublic interface WhiteList {\n    String[] sWhiteList = new String[]\n            {\n                    \"com.tencent.shadow.sample.host.lib\",\n            };\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/src/main/java/com/tencent/shadow/dynamic/loader/impl/CoreLoaderFactoryImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.sample.plugin.loader.SamplePluginLoader;\n\n/**\n * 这个类的包名类名是固定的。\n * <p>\n * 见com.tencent.shadow.dynamic.loader.impl.DynamicPluginLoader#CORE_LOADER_FACTORY_IMPL_NAME\n */\npublic class CoreLoaderFactoryImpl implements CoreLoaderFactory {\n    @Override\n    public ShadowPluginLoader build(Context hostAppContext) {\n        return new SamplePluginLoader(hostAppContext);\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/src/main/java/com/tencent/shadow/sample/plugin/loader/SampleComponentManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.loader;\n\nimport android.content.ComponentName;\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\npublic class SampleComponentManager extends ComponentManager {\n\n    /**\n     * dynamic-runtime-apk 模块中定义的壳子Activity，需要在宿主AndroidManifest.xml注册\n     */\n    private static final String DEFAULT_ACTIVITY = \"com.tencent.shadow.sample.plugin.runtime.PluginDefaultProxyActivity\";\n    private static final String SINGLE_INSTANCE_ACTIVITY = \"com.tencent.shadow.sample.plugin.runtime.PluginSingleInstance1ProxyActivity\";\n    private static final String SINGLE_TASK_ACTIVITY = \"com.tencent.shadow.sample.plugin.runtime.PluginSingleTask1ProxyActivity\";\n\n    private Context context;\n\n    public SampleComponentManager(Context context) {\n        this.context = context;\n    }\n\n\n    /**\n     * 配置插件Activity 到 壳子Activity的对应关系\n     *\n     * @param pluginActivity 插件Activity\n     * @return 壳子Activity\n     */\n    @Override\n    public ComponentName onBindContainerActivity(ComponentName pluginActivity) {\n        switch (pluginActivity.getClassName()) {\n            /**\n             * 这里配置对应的对应关系\n             */\n        }\n        return new ComponentName(context, DEFAULT_ACTIVITY);\n    }\n\n    /**\n     * 配置对应宿主中预注册的壳子contentProvider的信息\n     */\n    @Override\n    public ContainerProviderInfo onBindContainerContentProvider(ComponentName pluginContentProvider) {\n        return new ContainerProviderInfo(\n                \"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\",\n                context.getPackageName() + \".contentprovider.authority.dynamic\");\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-loader/src/main/java/com/tencent/shadow/sample/plugin/loader/SamplePluginLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.loader;\n\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.Resources;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.core.load_parameters.LoadParameters;\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader;\nimport com.tencent.shadow.core.loader.exceptions.LoadPluginException;\nimport com.tencent.shadow.core.loader.infos.PluginParts;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\nimport com.tencent.shadow.sample.host.lib.LoadPluginCallback;\n\nimport java.util.concurrent.Future;\n\nimport static android.content.pm.PackageManager.GET_META_DATA;\n\npublic class SamplePluginLoader extends ShadowPluginLoader {\n\n    private final static String TAG = \"shadow\";\n\n    private ComponentManager componentManager;\n\n    public SamplePluginLoader(Context hostAppContext) {\n        super(hostAppContext);\n        componentManager = new SampleComponentManager(hostAppContext);\n    }\n\n    @Override\n    public ComponentManager getComponentManager() {\n        return componentManager;\n    }\n\n    @Override\n    public Future<?> loadPlugin(final InstalledApk installedApk) throws LoadPluginException {\n        LoadParameters loadParameters = getLoadParameters(installedApk);\n        final String partKey = loadParameters.partKey;\n\n        LoadPluginCallback.getCallback().beforeLoadPlugin(partKey);\n\n        final Future<?> future = super.loadPlugin(installedApk);\n\n        getMExecutorService().submit(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    future.get();\n                    PluginParts pluginParts = getPluginParts(partKey);\n                    String packageName = pluginParts.getApplication().getPackageName();\n                    ApplicationInfo applicationInfo = pluginParts.getPluginPackageManager().getApplicationInfo(packageName, GET_META_DATA);\n                    PluginClassLoader classLoader = pluginParts.getClassLoader();\n                    Resources resources = pluginParts.getResources();\n\n                    LoadPluginCallback.getCallback().afterLoadPlugin(partKey, applicationInfo, classLoader, resources);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        });\n\n        return future;\n    }\n\n    @Override\n    public String getDelegateProviderKey() {\n        return \"SAMPLE\";\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.SAMPLE_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.core:activity-container'\n}\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.core.runtime.**{*;}\n\n-keep class * extends com.tencent.shadow.core.runtime.container.PluginContainerActivity"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.sample.plugin.runtime\" />\n\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/src/main/java/com/tencent/shadow/sample/plugin/runtime/PluginDefaultProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.runtime;\n\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginDefaultProxyActivity extends PluginContainerActivity {\n\n    @Override\n    protected String getDelegateProviderKey() {\n        return \"SAMPLE\";\n    }\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/src/main/java/com/tencent/shadow/sample/plugin/runtime/PluginSingleInstance1ProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.runtime;\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginSingleInstance1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/sample-runtime/src/main/java/com/tencent/shadow/sample/plugin/runtime/PluginSingleTask1ProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.sample.plugin.runtime;\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginSingleTask1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 singwhatiwanna\nhttps://github.com/singwhatiwanna\nhttp://blog.csdn.net/singwhatiwanna\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/README.md",
    "content": "https://github.com/singwhatiwanna/PinnedHeaderExpandableListView\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.ryg.expandable.ui\" />\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/src/main/java/com/ryg/expandable/ui/PinnedHeaderExpandableListView.java",
    "content": "/**\n * The MIT License (MIT)\n * <p>\n * Copyright (c) 2014 singwhatiwanna\n * https://github.com/singwhatiwanna\n * http://blog.csdn.net/singwhatiwanna\n * <p>\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * <p>\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n * <p>\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\npackage com.ryg.expandable.ui;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView;\nimport android.widget.AbsListView.OnScrollListener;\nimport android.widget.ExpandableListView;\n\npublic class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener {\n    private static final String TAG = \"PinnedHeaderExpandableListView\";\n    private static final boolean DEBUG = true;\n\n    public interface OnHeaderUpdateListener {\n        /**\n         * 返回一个view对象即可\n         * 注意：view必须要有LayoutParams\n         */\n        public View getPinnedHeader();\n\n        public void updatePinnedHeader(View headerView, int firstVisibleGroupPos);\n    }\n\n    private View mHeaderView;\n    private int mHeaderWidth;\n    private int mHeaderHeight;\n\n    private View mTouchTarget;\n\n    private OnScrollListener mScrollListener;\n    private OnHeaderUpdateListener mHeaderUpdateListener;\n\n    private boolean mActionDownHappened = false;\n    protected boolean mIsHeaderGroupClickable = true;\n\n\n    public PinnedHeaderExpandableListView(Context context) {\n        super(context);\n        initView();\n    }\n\n    public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initView();\n    }\n\n    public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        initView();\n    }\n\n    private void initView() {\n        setFadingEdgeLength(0);\n        setOnScrollListener(this);\n    }\n\n    @Override\n    public void setOnScrollListener(OnScrollListener l) {\n        if (l != this) {\n            mScrollListener = l;\n        } else {\n            mScrollListener = null;\n        }\n        super.setOnScrollListener(this);\n    }\n\n    /**\n     * 给group添加点击事件监听\n     *\n     * @param onGroupClickListener   监听\n     * @param isHeaderGroupClickable 表示header是否可点击<br/>\n     *                               note : 当不想group可点击的时候，需要在OnGroupClickListener#onGroupClick中返回true，\n     *                               并将isHeaderGroupClickable设为false即可\n     */\n    public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener, boolean isHeaderGroupClickable) {\n        mIsHeaderGroupClickable = isHeaderGroupClickable;\n        super.setOnGroupClickListener(onGroupClickListener);\n    }\n\n    public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {\n        mHeaderUpdateListener = listener;\n        if (listener == null) {\n            mHeaderView = null;\n            mHeaderWidth = mHeaderHeight = 0;\n            return;\n        }\n        mHeaderView = listener.getPinnedHeader();\n        int firstVisiblePos = getFirstVisiblePosition();\n        int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));\n        listener.updatePinnedHeader(mHeaderView, firstVisibleGroupPos);\n        requestLayout();\n        postInvalidate();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        if (mHeaderView == null) {\n            return;\n        }\n        measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);\n        mHeaderWidth = mHeaderView.getMeasuredWidth();\n        mHeaderHeight = mHeaderView.getMeasuredHeight();\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        super.onLayout(changed, l, t, r, b);\n        if (mHeaderView == null) {\n            return;\n        }\n        int delta = mHeaderView.getTop();\n        mHeaderView.layout(0, delta, mHeaderWidth, mHeaderHeight + delta);\n    }\n\n    @Override\n    protected void dispatchDraw(Canvas canvas) {\n        super.dispatchDraw(canvas);\n        if (mHeaderView != null) {\n            drawChild(canvas, mHeaderView, getDrawingTime());\n        }\n    }\n\n    @Override\n    public boolean dispatchTouchEvent(MotionEvent ev) {\n        int x = (int) ev.getX();\n        int y = (int) ev.getY();\n        int pos = pointToPosition(x, y);\n        if (mHeaderView != null && y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) {\n            if (ev.getAction() == MotionEvent.ACTION_DOWN) {\n                mTouchTarget = getTouchTarget(mHeaderView, x, y);\n                mActionDownHappened = true;\n            } else if (ev.getAction() == MotionEvent.ACTION_UP) {\n                View touchTarget = getTouchTarget(mHeaderView, x, y);\n                if (touchTarget == mTouchTarget && mTouchTarget.isClickable()) {\n                    mTouchTarget.performClick();\n                    invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));\n                } else if (mIsHeaderGroupClickable) {\n                    int groupPosition = getPackedPositionGroup(getExpandableListPosition(pos));\n                    if (groupPosition != INVALID_POSITION && mActionDownHappened) {\n                        if (isGroupExpanded(groupPosition)) {\n                            collapseGroup(groupPosition);\n                        } else {\n                            expandGroup(groupPosition);\n                        }\n                    }\n                }\n                mActionDownHappened = false;\n            }\n            return true;\n        }\n\n        return super.dispatchTouchEvent(ev);\n    }\n\n    private View getTouchTarget(View view, int x, int y) {\n        if (!(view instanceof ViewGroup)) {\n            return view;\n        }\n\n        ViewGroup parent = (ViewGroup) view;\n        int childrenCount = parent.getChildCount();\n        final boolean customOrder = isChildrenDrawingOrderEnabled();\n        View target = null;\n        for (int i = childrenCount - 1; i >= 0; i--) {\n            final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;\n            final View child = parent.getChildAt(childIndex);\n            if (isTouchPointInView(child, x, y)) {\n                target = child;\n                break;\n            }\n        }\n        if (target == null) {\n            target = parent;\n        }\n\n        return target;\n    }\n\n    private boolean isTouchPointInView(View view, int x, int y) {\n        if (view.isClickable() && y >= view.getTop() && y <= view.getBottom()\n                && x >= view.getLeft() && x <= view.getRight()) {\n            return true;\n        }\n        return false;\n    }\n\n    public void requestRefreshHeader() {\n        refreshHeader();\n        invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));\n    }\n\n    protected void refreshHeader() {\n        if (mHeaderView == null) {\n            return;\n        }\n        int firstVisiblePos = getFirstVisiblePosition();\n        int pos = firstVisiblePos + 1;\n        int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));\n        int group = getPackedPositionGroup(getExpandableListPosition(pos));\n        if (DEBUG) {\n            Log.d(TAG, \"refreshHeader firstVisibleGroupPos=\" + firstVisibleGroupPos);\n        }\n\n        if (group == firstVisibleGroupPos + 1) {\n            View view = getChildAt(1);\n            if (view == null) {\n                Log.w(TAG, \"Warning : refreshHeader getChildAt(1)=null\");\n                return;\n            }\n            if (view.getTop() <= mHeaderHeight) {\n                int delta = mHeaderHeight - view.getTop();\n                mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);\n            } else {\n                mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);\n            }\n        } else {\n            mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);\n        }\n\n        if (mHeaderUpdateListener != null) {\n            mHeaderUpdateListener.updatePinnedHeader(mHeaderView, firstVisibleGroupPos);\n        }\n    }\n\n    @Override\n    public void onScrollStateChanged(AbsListView view, int scrollState) {\n        if (mScrollListener != null) {\n            mScrollListener.onScrollStateChanged(view, scrollState);\n        }\n    }\n\n    @Override\n    public void onScroll(AbsListView view, int firstVisibleItem,\n                         int visibleItemCount, int totalItemCount) {\n        if (totalItemCount > 0) {\n            refreshHeader();\n        }\n        if (mScrollListener != null) {\n            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);\n        }\n    }\n\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview/src/main/java/com/ryg/expandable/ui/StickyLayout.java",
    "content": "/**\n * The MIT License (MIT)\n * <p>\n * Copyright (c) 2014 singwhatiwanna\n * https://github.com/singwhatiwanna\n * http://blog.csdn.net/singwhatiwanna\n * <p>\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * <p>\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n * <p>\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\npackage com.ryg.expandable.ui;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.widget.LinearLayout;\n\nimport java.util.NoSuchElementException;\n\n\npublic class StickyLayout extends LinearLayout {\n    private static final String TAG = \"StickyLayout\";\n    private static final boolean DEBUG = true;\n\n    public interface OnGiveUpTouchEventListener {\n        public boolean giveUpTouchEvent(MotionEvent event);\n    }\n\n    private View mHeader;\n    private View mContent;\n    private OnGiveUpTouchEventListener mGiveUpTouchEventListener;\n\n    // header的高度  单位：px\n    private int mOriginalHeaderHeight;\n    private int mHeaderHeight;\n\n    private int mStatus = STATUS_EXPANDED;\n    public static final int STATUS_EXPANDED = 1;\n    public static final int STATUS_COLLAPSED = 2;\n\n    private int mTouchSlop;\n\n    // 分别记录上次滑动的坐标\n    private int mLastX = 0;\n    private int mLastY = 0;\n\n    // 分别记录上次滑动的坐标(onInterceptTouchEvent)\n    private int mLastXIntercept = 0;\n    private int mLastYIntercept = 0;\n\n    // 用来控制滑动角度，仅当角度a满足如下条件才进行滑动：tan a = deltaX / deltaY > 2\n    private static final int TAN = 2;\n\n    private boolean mIsSticky = true;\n    private boolean mInitDataSucceed = false;\n    private boolean mDisallowInterceptTouchEventOnHeader = true;\n\n    public StickyLayout(Context context) {\n        super(context);\n    }\n\n    public StickyLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public StickyLayout(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasWindowFocus) {\n        super.onWindowFocusChanged(hasWindowFocus);\n        if (hasWindowFocus && (mHeader == null || mContent == null)) {\n            initData();\n        }\n    }\n\n    private void initData() {\n        int headerId = getResources().getIdentifier(\"sticky_header\", \"id\", getContext().getPackageName());\n        int contentId = getResources().getIdentifier(\"sticky_content\", \"id\", getContext().getPackageName());\n        if (headerId != 0 && contentId != 0) {\n            mHeader = findViewById(headerId);\n            mContent = findViewById(contentId);\n            mOriginalHeaderHeight = mHeader.getMeasuredHeight();\n            mHeaderHeight = mOriginalHeaderHeight;\n            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();\n            if (mHeaderHeight > 0) {\n                mInitDataSucceed = true;\n            }\n            if (DEBUG) {\n                Log.d(TAG, \"mTouchSlop = \" + mTouchSlop + \"mHeaderHeight = \" + mHeaderHeight);\n            }\n        } else {\n            throw new NoSuchElementException(\"Did your view with id \\\"sticky_header\\\" or \\\"sticky_content\\\" exists?\");\n        }\n    }\n\n    public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {\n        mGiveUpTouchEventListener = l;\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent event) {\n        int intercepted = 0;\n        int x = (int) event.getX();\n        int y = (int) event.getY();\n\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN: {\n                mLastXIntercept = x;\n                mLastYIntercept = y;\n                mLastX = x;\n                mLastY = y;\n                intercepted = 0;\n                break;\n            }\n            case MotionEvent.ACTION_MOVE: {\n                int deltaX = x - mLastXIntercept;\n                int deltaY = y - mLastYIntercept;\n                if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) {\n                    intercepted = 0;\n                } else if (Math.abs(deltaY) <= Math.abs(deltaX)) {\n                    intercepted = 0;\n                } else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {\n                    intercepted = 1;\n                } else if (mGiveUpTouchEventListener != null) {\n                    if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {\n                        intercepted = 1;\n                    }\n                }\n                break;\n            }\n            case MotionEvent.ACTION_UP: {\n                intercepted = 0;\n                mLastXIntercept = mLastYIntercept = 0;\n                break;\n            }\n            default:\n                break;\n        }\n\n        if (DEBUG) {\n            Log.d(TAG, \"intercepted=\" + intercepted);\n        }\n        return intercepted != 0 && mIsSticky;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (!mIsSticky) {\n            return true;\n        }\n        int x = (int) event.getX();\n        int y = (int) event.getY();\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN: {\n                break;\n            }\n            case MotionEvent.ACTION_MOVE: {\n                int deltaX = x - mLastX;\n                int deltaY = y - mLastY;\n                if (DEBUG) {\n                    Log.d(TAG, \"mHeaderHeight=\" + mHeaderHeight + \"  deltaY=\" + deltaY + \"  mlastY=\" + mLastY);\n                }\n                mHeaderHeight += deltaY;\n                setHeaderHeight(mHeaderHeight);\n                break;\n            }\n            case MotionEvent.ACTION_UP: {\n                // 这里做了下判断，当松开手的时候，会自动向两边滑动，具体向哪边滑，要看当前所处的位置\n                int destHeight = 0;\n                if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {\n                    destHeight = 0;\n                    mStatus = STATUS_COLLAPSED;\n                } else {\n                    destHeight = mOriginalHeaderHeight;\n                    mStatus = STATUS_EXPANDED;\n                }\n                // 慢慢滑向终点\n                this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);\n                break;\n            }\n            default:\n                break;\n        }\n        mLastX = x;\n        mLastY = y;\n        return true;\n    }\n\n    public void smoothSetHeaderHeight(final int from, final int to, long duration) {\n        smoothSetHeaderHeight(from, to, duration, false);\n    }\n\n    public void smoothSetHeaderHeight(final int from, final int to, long duration, final boolean modifyOriginalHeaderHeight) {\n        final int frameCount = (int) (duration / 1000f * 30) + 1;\n        final float partation = (to - from) / (float) frameCount;\n        new Thread(\"Thread#smoothSetHeaderHeight\") {\n\n            @Override\n            public void run() {\n                for (int i = 0; i < frameCount; i++) {\n                    final int height;\n                    if (i == frameCount - 1) {\n                        height = to;\n                    } else {\n                        height = (int) (from + partation * i);\n                    }\n                    post(new Runnable() {\n                        public void run() {\n                            setHeaderHeight(height);\n                        }\n                    });\n                    try {\n                        sleep(10);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n\n                if (modifyOriginalHeaderHeight) {\n                    setOriginalHeaderHeight(to);\n                }\n            }\n\n            ;\n\n        }.start();\n    }\n\n    public void setOriginalHeaderHeight(int originalHeaderHeight) {\n        mOriginalHeaderHeight = originalHeaderHeight;\n    }\n\n    public void setHeaderHeight(int height, boolean modifyOriginalHeaderHeight) {\n        if (modifyOriginalHeaderHeight) {\n            setOriginalHeaderHeight(height);\n        }\n        setHeaderHeight(height);\n    }\n\n    public void setHeaderHeight(int height) {\n        if (!mInitDataSucceed) {\n            initData();\n        }\n\n        if (DEBUG) {\n            Log.d(TAG, \"setHeaderHeight height=\" + height);\n        }\n        if (height <= 0) {\n            height = 0;\n        } else if (height > mOriginalHeaderHeight) {\n            height = mOriginalHeaderHeight;\n        }\n\n        if (height == 0) {\n            mStatus = STATUS_COLLAPSED;\n        } else {\n            mStatus = STATUS_EXPANDED;\n        }\n\n        if (mHeader != null && mHeader.getLayoutParams() != null) {\n            mHeader.getLayoutParams().height = height;\n            mHeader.requestLayout();\n            mHeaderHeight = height;\n        } else {\n            if (DEBUG) {\n                Log.e(TAG, \"null LayoutParams when setHeaderHeight\");\n            }\n        }\n    }\n\n    public int getHeaderHeight() {\n        return mHeaderHeight;\n    }\n\n    public void setSticky(boolean isSticky) {\n        mIsSticky = isSticky;\n    }\n\n    public void requestDisallowInterceptTouchEventOnHeader(boolean disallowIntercept) {\n        mDisallowInterceptTouchEventOnHeader = disallowIntercept;\n    }\n\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/README.md",
    "content": "https://github.com/jfeinstein10/SlidingMenu\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies{\n    implementation 'com.android.support:support-fragment:27.0.2'\n}"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.jeremyfeinstein.slidingmenu.lib\" />\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/java/com/jeremyfeinstein/slidingmenu/lib/CanvasTransformerBuilder.java",
    "content": "package com.jeremyfeinstein.slidingmenu.lib;\n\nimport android.graphics.Canvas;\nimport android.view.animation.Interpolator;\n\nimport com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.CanvasTransformer;\n\npublic class CanvasTransformerBuilder {\n\n    private CanvasTransformer mTrans;\n\n    private static Interpolator lin = new Interpolator() {\n        public float getInterpolation(float t) {\n            return t;\n        }\n    };\n\n    private void initTransformer() {\n        if (mTrans == null)\n            mTrans = new CanvasTransformer() {\n                public void transformCanvas(Canvas canvas, float percentOpen) {\n                }\n            };\n    }\n\n    public CanvasTransformer zoom(final int openedX, final int closedX,\n                                  final int openedY, final int closedY,\n                                  final int px, final int py) {\n        return zoom(openedX, closedX, openedY, closedY, px, py, lin);\n    }\n\n    public CanvasTransformer zoom(final int openedX, final int closedX,\n                                  final int openedY, final int closedY,\n                                  final int px, final int py, final Interpolator interp) {\n        initTransformer();\n        mTrans = new CanvasTransformer() {\n            public void transformCanvas(Canvas canvas, float percentOpen) {\n                mTrans.transformCanvas(canvas, percentOpen);\n                float f = interp.getInterpolation(percentOpen);\n                canvas.scale((openedX - closedX) * f + closedX,\n                        (openedY - closedY) * f + closedY, px, py);\n            }\n        };\n        return mTrans;\n    }\n\n    public CanvasTransformer rotate(final int openedDeg, final int closedDeg,\n                                    final int px, final int py) {\n        return rotate(openedDeg, closedDeg, px, py, lin);\n    }\n\n    public CanvasTransformer rotate(final int openedDeg, final int closedDeg,\n                                    final int px, final int py, final Interpolator interp) {\n        initTransformer();\n        mTrans = new CanvasTransformer() {\n            public void transformCanvas(Canvas canvas, float percentOpen) {\n                mTrans.transformCanvas(canvas, percentOpen);\n                float f = interp.getInterpolation(percentOpen);\n                canvas.rotate((openedDeg - closedDeg) * f + closedDeg,\n                        px, py);\n            }\n        };\n        return mTrans;\n    }\n\n    public CanvasTransformer translate(final int openedX, final int closedX,\n                                       final int openedY, final int closedY) {\n        return translate(openedX, closedX, openedY, closedY, lin);\n    }\n\n    public CanvasTransformer translate(final int openedX, final int closedX,\n                                       final int openedY, final int closedY, final Interpolator interp) {\n        initTransformer();\n        mTrans = new CanvasTransformer() {\n            public void transformCanvas(Canvas canvas, float percentOpen) {\n                mTrans.transformCanvas(canvas, percentOpen);\n                float f = interp.getInterpolation(percentOpen);\n                canvas.translate((openedX - closedX) * f + closedX,\n                        (openedY - closedY) * f + closedY);\n            }\n        };\n        return mTrans;\n    }\n\n    public CanvasTransformer concatTransformer(final CanvasTransformer t) {\n        initTransformer();\n        mTrans = new CanvasTransformer() {\n            public void transformCanvas(Canvas canvas, float percentOpen) {\n                mTrans.transformCanvas(canvas, percentOpen);\n                t.transformCanvas(canvas, percentOpen);\n            }\n        };\n        return mTrans;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/java/com/jeremyfeinstein/slidingmenu/lib/CustomViewAbove.java",
    "content": "package com.jeremyfeinstein.slidingmenu.lib;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.support.v4.view.MotionEventCompat;\nimport android.support.v4.view.VelocityTrackerCompat;\nimport android.support.v4.view.ViewCompat;\nimport android.support.v4.view.ViewConfigurationCompat;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.FocusFinder;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.SoundEffectConstants;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.view.animation.Interpolator;\nimport android.widget.Scroller;\n\nimport com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.OnClosedListener;\nimport com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.OnOpenedListener;\n\nimport java.util.ArrayList;\nimport java.util.List;\n//import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.OnCloseListener;\n//import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.OnOpenListener;\n\npublic class CustomViewAbove extends ViewGroup {\n\n    private static final String TAG = \"CustomViewAbove\";\n    private static final boolean DEBUG = false;\n\n    private static final boolean USE_CACHE = false;\n\n    private static final int MAX_SETTLE_DURATION = 600; // ms\n    private static final int MIN_DISTANCE_FOR_FLING = 25; // dips\n\n    private static final Interpolator sInterpolator = new Interpolator() {\n        public float getInterpolation(float t) {\n            t -= 1.0f;\n            return t * t * t * t * t + 1.0f;\n        }\n    };\n\n    private View mContent;\n\n    private int mCurItem;\n    private Scroller mScroller;\n\n    private boolean mScrollingCacheEnabled;\n\n    private boolean mScrolling;\n\n    private boolean mIsBeingDragged;\n    private boolean mIsUnableToDrag;\n    private int mTouchSlop;\n    private float mInitialMotionX;\n    /**\n     * Position of the last motion event.\n     */\n    private float mLastMotionX;\n    private float mLastMotionY;\n    /**\n     * ID of the active pointer. This is used to retain consistency during\n     * drags/flings if multiple pointers are used.\n     */\n    protected int mActivePointerId = INVALID_POINTER;\n    /**\n     * Sentinel value for no current active pointer.\n     * Used by {@link #mActivePointerId}.\n     */\n    private static final int INVALID_POINTER = -1;\n\n    /**\n     * Determines speed during touch scrolling\n     */\n    protected VelocityTracker mVelocityTracker;\n    private int mMinimumVelocity;\n    protected int mMaximumVelocity;\n    private int mFlingDistance;\n\n    private CustomViewBehind mViewBehind;\n    //\tprivate int mMode;\n    private boolean mEnabled = true;\n\n    private OnPageChangeListener mOnPageChangeListener;\n    private OnPageChangeListener mInternalPageChangeListener;\n\n    //\tprivate OnCloseListener mCloseListener;\n    //\tprivate OnOpenListener mOpenListener;\n    private OnClosedListener mClosedListener;\n    private OnOpenedListener mOpenedListener;\n\n    private List<View> mIgnoredViews = new ArrayList<View>();\n\n    //\tprivate int mScrollState = SCROLL_STATE_IDLE;\n\n    /**\n     * Callback interface for responding to changing state of the selected page.\n     */\n    public interface OnPageChangeListener {\n\n        /**\n         * This method will be invoked when the current page is scrolled, either as part\n         * of a programmatically initiated smooth scroll or a user initiated touch scroll.\n         *\n         * @param position             Position index of the first page currently being displayed.\n         *                             Page position+1 will be visible if positionOffset is nonzero.\n         * @param positionOffset       Value from [0, 1) indicating the offset from the page at position.\n         * @param positionOffsetPixels Value in pixels indicating the offset from position.\n         */\n        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);\n\n        /**\n         * This method will be invoked when a new page becomes selected. Animation is not\n         * necessarily complete.\n         *\n         * @param position Position index of the new selected page.\n         */\n        public void onPageSelected(int position);\n\n    }\n\n    /**\n     * Simple implementation of the {@link OnPageChangeListener} interface with stub\n     * implementations of each method. Extend this if you do not intend to override\n     * every method of {@link OnPageChangeListener}.\n     */\n    public static class SimpleOnPageChangeListener implements OnPageChangeListener {\n\n        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\n            // This space for rent\n        }\n\n        public void onPageSelected(int position) {\n            // This space for rent\n        }\n\n        public void onPageScrollStateChanged(int state) {\n            // This space for rent\n        }\n\n    }\n\n    public CustomViewAbove(Context context) {\n        this(context, null);\n    }\n\n    public CustomViewAbove(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        initCustomViewAbove();\n    }\n\n    void initCustomViewAbove() {\n        setWillNotDraw(false);\n        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);\n        setFocusable(true);\n        final Context context = getContext();\n        mScroller = new Scroller(context, sInterpolator);\n        final ViewConfiguration configuration = ViewConfiguration.get(context);\n        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);\n        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();\n        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();\n        setInternalPageChangeListener(new SimpleOnPageChangeListener() {\n            public void onPageSelected(int position) {\n                if (mViewBehind != null) {\n                    switch (position) {\n                        case 0:\n                        case 2:\n                            mViewBehind.setChildrenEnabled(true);\n                            break;\n                        case 1:\n                            mViewBehind.setChildrenEnabled(false);\n                            break;\n                    }\n                }\n            }\n        });\n\n        final float density = context.getResources().getDisplayMetrics().density;\n        mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);\n    }\n\n    /**\n     * Set the currently selected page. If the CustomViewPager has already been through its first\n     * layout there will be a smooth animated transition between the current item and the\n     * specified item.\n     *\n     * @param item Item index to select\n     */\n    public void setCurrentItem(int item) {\n        setCurrentItemInternal(item, true, false);\n    }\n\n    /**\n     * Set the currently selected page.\n     *\n     * @param item         Item index to select\n     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately\n     */\n    public void setCurrentItem(int item, boolean smoothScroll) {\n        setCurrentItemInternal(item, smoothScroll, false);\n    }\n\n    public int getCurrentItem() {\n        return mCurItem;\n    }\n\n    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {\n        setCurrentItemInternal(item, smoothScroll, always, 0);\n    }\n\n    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {\n        if (!always && mCurItem == item) {\n            setScrollingCacheEnabled(false);\n            return;\n        }\n\n        item = mViewBehind.getMenuPage(item);\n\n        final boolean dispatchSelected = mCurItem != item;\n        mCurItem = item;\n        final int destX = getDestScrollX(mCurItem);\n        if (dispatchSelected && mOnPageChangeListener != null) {\n            mOnPageChangeListener.onPageSelected(item);\n        }\n        if (dispatchSelected && mInternalPageChangeListener != null) {\n            mInternalPageChangeListener.onPageSelected(item);\n        }\n        if (smoothScroll) {\n            smoothScrollTo(destX, 0, velocity);\n        } else {\n            completeScroll();\n            scrollTo(destX, 0);\n        }\n    }\n\n    /**\n     * Set a listener that will be invoked whenever the page changes or is incrementally\n     * scrolled. See {@link OnPageChangeListener}.\n     *\n     * @param listener Listener to set\n     */\n    public void setOnPageChangeListener(OnPageChangeListener listener) {\n        mOnPageChangeListener = listener;\n    }\n\n    /*\n    public void setOnOpenListener(OnOpenListener l) {\n        mOpenListener = l;\n    }\n\n    public void setOnCloseListener(OnCloseListener l) {\n        mCloseListener = l;\n    }\n     */\n    public void setOnOpenedListener(OnOpenedListener l) {\n        mOpenedListener = l;\n    }\n\n    public void setOnClosedListener(OnClosedListener l) {\n        mClosedListener = l;\n    }\n\n    /**\n     * Set a separate OnPageChangeListener for internal use by the support library.\n     *\n     * @param listener Listener to set\n     * @return The old listener that was set, if any.\n     */\n    OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {\n        OnPageChangeListener oldListener = mInternalPageChangeListener;\n        mInternalPageChangeListener = listener;\n        return oldListener;\n    }\n\n    public void addIgnoredView(View v) {\n        if (!mIgnoredViews.contains(v)) {\n            mIgnoredViews.add(v);\n        }\n    }\n\n    public void removeIgnoredView(View v) {\n        mIgnoredViews.remove(v);\n    }\n\n    public void clearIgnoredViews() {\n        mIgnoredViews.clear();\n    }\n\n    // We want the duration of the page snap animation to be influenced by the distance that\n    // the screen has to travel, however, we don't want this duration to be effected in a\n    // purely linear fashion. Instead, we use this method to moderate the effect that the distance\n    // of travel has on the overall snap duration.\n    float distanceInfluenceForSnapDuration(float f) {\n        f -= 0.5f; // center the values about 0.\n        f *= 0.3f * Math.PI / 2.0f;\n        return (float) Math.sin(f);\n    }\n\n    public int getDestScrollX(int page) {\n        switch (page) {\n            case 0:\n            case 2:\n                return mViewBehind.getMenuLeft(mContent, page);\n            case 1:\n                return mContent.getLeft();\n        }\n        return 0;\n    }\n\n    private int getLeftBound() {\n        return mViewBehind.getAbsLeftBound(mContent);\n    }\n\n    private int getRightBound() {\n        return mViewBehind.getAbsRightBound(mContent);\n    }\n\n    public int getContentLeft() {\n        return mContent.getLeft() + mContent.getPaddingLeft();\n    }\n\n    public boolean isMenuOpen() {\n        return mCurItem == 0 || mCurItem == 2;\n    }\n\n    private boolean isInIgnoredView(MotionEvent ev) {\n        Rect rect = new Rect();\n        for (View v : mIgnoredViews) {\n            v.getHitRect(rect);\n            if (rect.contains((int) ev.getX(), (int) ev.getY())) return true;\n        }\n        return false;\n    }\n\n    public int getBehindWidth() {\n        if (mViewBehind == null) {\n            return 0;\n        } else {\n            return mViewBehind.getBehindWidth();\n        }\n    }\n\n    public int getChildWidth(int i) {\n        switch (i) {\n            case 0:\n                return getBehindWidth();\n            case 1:\n                return mContent.getWidth();\n            default:\n                return 0;\n        }\n    }\n\n    public boolean isSlidingEnabled() {\n        return mEnabled;\n    }\n\n    public void setSlidingEnabled(boolean b) {\n        mEnabled = b;\n    }\n\n    /**\n     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\n     *\n     * @param x the number of pixels to scroll by on the X axis\n     * @param y the number of pixels to scroll by on the Y axis\n     */\n    void smoothScrollTo(int x, int y) {\n        smoothScrollTo(x, y, 0);\n    }\n\n    /**\n     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\n     *\n     * @param x        the number of pixels to scroll by on the X axis\n     * @param y        the number of pixels to scroll by on the Y axis\n     * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)\n     */\n    void smoothScrollTo(int x, int y, int velocity) {\n        if (getChildCount() == 0) {\n            // Nothing to do.\n            setScrollingCacheEnabled(false);\n            return;\n        }\n        int sx = getScrollX();\n        int sy = getScrollY();\n        int dx = x - sx;\n        int dy = y - sy;\n        if (dx == 0 && dy == 0) {\n            completeScroll();\n            if (isMenuOpen()) {\n                if (mOpenedListener != null)\n                    mOpenedListener.onOpened();\n            } else {\n                if (mClosedListener != null)\n                    mClosedListener.onClosed();\n            }\n            return;\n        }\n\n        setScrollingCacheEnabled(true);\n        mScrolling = true;\n\n        final int width = getBehindWidth();\n        final int halfWidth = width / 2;\n        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);\n        final float distance = halfWidth + halfWidth *\n                distanceInfluenceForSnapDuration(distanceRatio);\n\n        int duration = 0;\n        velocity = Math.abs(velocity);\n        if (velocity > 0) {\n            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\n        } else {\n            final float pageDelta = (float) Math.abs(dx) / width;\n            duration = (int) ((pageDelta + 1) * 100);\n            duration = MAX_SETTLE_DURATION;\n        }\n        duration = Math.min(duration, MAX_SETTLE_DURATION);\n\n        mScroller.startScroll(sx, sy, dx, dy, duration);\n        invalidate();\n    }\n\n    public void setContent(View v) {\n        if (mContent != null)\n            this.removeView(mContent);\n        mContent = v;\n        addView(mContent);\n    }\n\n    public View getContent() {\n        return mContent;\n    }\n\n    public void setCustomViewBehind(CustomViewBehind cvb) {\n        mViewBehind = cvb;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\n        int width = getDefaultSize(0, widthMeasureSpec);\n        int height = getDefaultSize(0, heightMeasureSpec);\n        setMeasuredDimension(width, height);\n\n        final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);\n        final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);\n        mContent.measure(contentWidth, contentHeight);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        // Make sure scroll position is set correctly.\n        if (w != oldw) {\n            // [ChrisJ] - This fixes the onConfiguration change for orientation issue..\n            // maybe worth having a look why the recomputeScroll pos is screwing\n            // up?\n            completeScroll();\n            scrollTo(getDestScrollX(mCurItem), getScrollY());\n        }\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        final int width = r - l;\n        final int height = b - t;\n        mContent.layout(0, 0, width, height);\n    }\n\n    public void setAboveOffset(int i) {\n        //\t\tRelativeLayout.LayoutParams params = ((RelativeLayout.LayoutParams)mContent.getLayoutParams());\n        //\t\tparams.setMargins(i, params.topMargin, params.rightMargin, params.bottomMargin);\n        mContent.setPadding(i, mContent.getPaddingTop(),\n                mContent.getPaddingRight(), mContent.getPaddingBottom());\n    }\n\n\n    @Override\n    public void computeScroll() {\n        if (!mScroller.isFinished()) {\n            if (mScroller.computeScrollOffset()) {\n                int oldX = getScrollX();\n                int oldY = getScrollY();\n                int x = mScroller.getCurrX();\n                int y = mScroller.getCurrY();\n\n                if (oldX != x || oldY != y) {\n                    scrollTo(x, y);\n                    pageScrolled(x);\n                }\n\n                // Keep on drawing until the animation has finished.\n                invalidate();\n                return;\n            }\n        }\n\n        // Done with scroll, clean up state.\n        completeScroll();\n    }\n\n    private void pageScrolled(int xpos) {\n        final int widthWithMargin = getWidth();\n        final int position = xpos / widthWithMargin;\n        final int offsetPixels = xpos % widthWithMargin;\n        final float offset = (float) offsetPixels / widthWithMargin;\n\n        onPageScrolled(position, offset, offsetPixels);\n    }\n\n    /**\n     * This method will be invoked when the current page is scrolled, either as part\n     * of a programmatically initiated smooth scroll or a user initiated touch scroll.\n     * If you override this method you must call through to the superclass implementation\n     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled\n     * returns.\n     *\n     * @param position     Position index of the first page currently being displayed.\n     *                     Page position+1 will be visible if positionOffset is nonzero.\n     * @param offset       Value from [0, 1) indicating the offset from the page at position.\n     * @param offsetPixels Value in pixels indicating the offset from position.\n     */\n    protected void onPageScrolled(int position, float offset, int offsetPixels) {\n        if (mOnPageChangeListener != null) {\n            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);\n        }\n        if (mInternalPageChangeListener != null) {\n            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);\n        }\n    }\n\n    private void completeScroll() {\n        boolean needPopulate = mScrolling;\n        if (needPopulate) {\n            // Done with scroll, no longer want to cache view drawing.\n            setScrollingCacheEnabled(false);\n            mScroller.abortAnimation();\n            int oldX = getScrollX();\n            int oldY = getScrollY();\n            int x = mScroller.getCurrX();\n            int y = mScroller.getCurrY();\n            if (oldX != x || oldY != y) {\n                scrollTo(x, y);\n            }\n            if (isMenuOpen()) {\n                if (mOpenedListener != null)\n                    mOpenedListener.onOpened();\n            } else {\n                if (mClosedListener != null)\n                    mClosedListener.onClosed();\n            }\n        }\n        mScrolling = false;\n    }\n\n    protected int mTouchMode = SlidingMenu.TOUCHMODE_MARGIN;\n\n    public void setTouchMode(int i) {\n        mTouchMode = i;\n    }\n\n    public int getTouchMode() {\n        return mTouchMode;\n    }\n\n    private boolean thisTouchAllowed(MotionEvent ev) {\n        int x = (int) (ev.getX() + mScrollX);\n        if (isMenuOpen()) {\n            return mViewBehind.menuOpenTouchAllowed(mContent, mCurItem, x);\n        } else {\n            switch (mTouchMode) {\n                case SlidingMenu.TOUCHMODE_FULLSCREEN:\n                    return !isInIgnoredView(ev);\n                case SlidingMenu.TOUCHMODE_NONE:\n                    return false;\n                case SlidingMenu.TOUCHMODE_MARGIN:\n                    return mViewBehind.marginTouchAllowed(mContent, x);\n            }\n        }\n        return false;\n    }\n\n    private boolean thisSlideAllowed(float dx) {\n        boolean allowed = false;\n        if (isMenuOpen()) {\n            allowed = mViewBehind.menuOpenSlideAllowed(dx);\n        } else {\n            allowed = mViewBehind.menuClosedSlideAllowed(dx);\n        }\n        if (DEBUG)\n            Log.v(TAG, \"this slide allowed \" + allowed + \" dx: \" + dx);\n        return allowed;\n    }\n\n    private int getPointerIndex(MotionEvent ev, int id) {\n        int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id);\n        if (activePointerIndex == -1)\n            mActivePointerId = INVALID_POINTER;\n        return activePointerIndex;\n    }\n\n    private boolean mQuickReturn = false;\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n\n        if (!mEnabled)\n            return false;\n\n        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;\n\n        if (DEBUG)\n            if (action == MotionEvent.ACTION_DOWN)\n                Log.v(TAG, \"Received ACTION_DOWN\");\n\n        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP\n                || (action != MotionEvent.ACTION_DOWN && mIsUnableToDrag)) {\n            endDrag();\n            return false;\n        }\n\n        switch (action) {\n            case MotionEvent.ACTION_MOVE:\n                determineDrag(ev);\n                break;\n            case MotionEvent.ACTION_DOWN:\n                int index = MotionEventCompat.getActionIndex(ev);\n                mActivePointerId = MotionEventCompat.getPointerId(ev, index);\n                if (mActivePointerId == INVALID_POINTER)\n                    break;\n                mLastMotionX = mInitialMotionX = MotionEventCompat.getX(ev, index);\n                mLastMotionY = MotionEventCompat.getY(ev, index);\n                if (thisTouchAllowed(ev)) {\n                    mIsBeingDragged = false;\n                    mIsUnableToDrag = false;\n                    if (isMenuOpen() && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) {\n                        mQuickReturn = true;\n                    }\n                } else {\n                    mIsUnableToDrag = true;\n                }\n                break;\n            case MotionEventCompat.ACTION_POINTER_UP:\n                onSecondaryPointerUp(ev);\n                break;\n        }\n\n        if (!mIsBeingDragged) {\n            if (mVelocityTracker == null) {\n                mVelocityTracker = VelocityTracker.obtain();\n            }\n            mVelocityTracker.addMovement(ev);\n        }\n        return mIsBeingDragged || mQuickReturn;\n    }\n\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n\n        if (!mEnabled)\n            return false;\n\n        if (!mIsBeingDragged && !thisTouchAllowed(ev))\n            return false;\n\n        //\t\tif (!mIsBeingDragged && !mQuickReturn)\n        //\t\t\treturn false;\n\n        final int action = ev.getAction();\n\n        if (mVelocityTracker == null) {\n            mVelocityTracker = VelocityTracker.obtain();\n        }\n        mVelocityTracker.addMovement(ev);\n\n        switch (action & MotionEventCompat.ACTION_MASK) {\n            case MotionEvent.ACTION_DOWN:\n                /*\n                 * If being flinged and user touches, stop the fling. isFinished\n                 * will be false if being flinged.\n                 */\n                completeScroll();\n\n                // Remember where the motion event started\n                int index = MotionEventCompat.getActionIndex(ev);\n                mActivePointerId = MotionEventCompat.getPointerId(ev, index);\n                mLastMotionX = mInitialMotionX = ev.getX();\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (!mIsBeingDragged) {\n                    determineDrag(ev);\n                    if (mIsUnableToDrag)\n                        return false;\n                }\n                if (mIsBeingDragged) {\n                    // Scroll to follow the motion event\n                    final int activePointerIndex = getPointerIndex(ev, mActivePointerId);\n                    if (mActivePointerId == INVALID_POINTER)\n                        break;\n                    final float x = MotionEventCompat.getX(ev, activePointerIndex);\n                    final float deltaX = mLastMotionX - x;\n                    mLastMotionX = x;\n                    float oldScrollX = getScrollX();\n                    float scrollX = oldScrollX + deltaX;\n                    final float leftBound = getLeftBound();\n                    final float rightBound = getRightBound();\n                    if (scrollX < leftBound) {\n                        scrollX = leftBound;\n                    } else if (scrollX > rightBound) {\n                        scrollX = rightBound;\n                    }\n                    // Don't lose the rounded component\n                    mLastMotionX += scrollX - (int) scrollX;\n                    scrollTo((int) scrollX, getScrollY());\n                    pageScrolled((int) scrollX);\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n                if (mIsBeingDragged) {\n                    final VelocityTracker velocityTracker = mVelocityTracker;\n                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\n                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(\n                            velocityTracker, mActivePointerId);\n                    final int scrollX = getScrollX();\n                    final float pageOffset = (float) (scrollX - getDestScrollX(mCurItem)) / getBehindWidth();\n                    final int activePointerIndex = getPointerIndex(ev, mActivePointerId);\n                    if (mActivePointerId != INVALID_POINTER) {\n                        final float x = MotionEventCompat.getX(ev, activePointerIndex);\n                        final int totalDelta = (int) (x - mInitialMotionX);\n                        int nextPage = determineTargetPage(pageOffset, initialVelocity, totalDelta);\n                        setCurrentItemInternal(nextPage, true, true, initialVelocity);\n                    } else {\n                        setCurrentItemInternal(mCurItem, true, true, initialVelocity);\n                    }\n                    mActivePointerId = INVALID_POINTER;\n                    endDrag();\n                } else if (mQuickReturn && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) {\n                    // close the menu\n                    setCurrentItem(1);\n                    endDrag();\n                }\n                break;\n            case MotionEvent.ACTION_CANCEL:\n                if (mIsBeingDragged) {\n                    setCurrentItemInternal(mCurItem, true, true);\n                    mActivePointerId = INVALID_POINTER;\n                    endDrag();\n                }\n                break;\n            case MotionEventCompat.ACTION_POINTER_DOWN: {\n                final int indexx = MotionEventCompat.getActionIndex(ev);\n                mLastMotionX = MotionEventCompat.getX(ev, indexx);\n                mActivePointerId = MotionEventCompat.getPointerId(ev, indexx);\n                break;\n            }\n            case MotionEventCompat.ACTION_POINTER_UP:\n                onSecondaryPointerUp(ev);\n                int pointerIndex = getPointerIndex(ev, mActivePointerId);\n                if (mActivePointerId == INVALID_POINTER)\n                    break;\n                mLastMotionX = MotionEventCompat.getX(ev, pointerIndex);\n                break;\n        }\n        return true;\n    }\n\n    private void determineDrag(MotionEvent ev) {\n        final int activePointerId = mActivePointerId;\n        final int pointerIndex = getPointerIndex(ev, activePointerId);\n        if (activePointerId == INVALID_POINTER || pointerIndex == INVALID_POINTER)\n            return;\n        final float x = MotionEventCompat.getX(ev, pointerIndex);\n        final float dx = x - mLastMotionX;\n        final float xDiff = Math.abs(dx);\n        final float y = MotionEventCompat.getY(ev, pointerIndex);\n        final float dy = y - mLastMotionY;\n        final float yDiff = Math.abs(dy);\n        if (xDiff > (isMenuOpen() ? mTouchSlop / 2 : mTouchSlop) && xDiff > yDiff && thisSlideAllowed(dx)) {\n            startDrag();\n            mLastMotionX = x;\n            mLastMotionY = y;\n            setScrollingCacheEnabled(true);\n        } else if (xDiff > mTouchSlop) {\n            mIsUnableToDrag = true;\n        }\n    }\n\n    @Override\n    public void scrollTo(int x, int y) {\n        super.scrollTo(x, y);\n        mScrollX = x;\n        mViewBehind.scrollBehindTo(mContent, x, y);\n        ((SlidingMenu) getParent()).manageLayers(getPercentOpen());\n    }\n\n    private int determineTargetPage(float pageOffset, int velocity, int deltaX) {\n        int targetPage = mCurItem;\n        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {\n            if (velocity > 0 && deltaX > 0) {\n                targetPage -= 1;\n            } else if (velocity < 0 && deltaX < 0) {\n                targetPage += 1;\n            }\n        } else {\n            targetPage = (int) Math.round(mCurItem + pageOffset);\n        }\n        return targetPage;\n    }\n\n    protected float getPercentOpen() {\n        return Math.abs(mScrollX - mContent.getLeft()) / getBehindWidth();\n    }\n\n    @Override\n    protected void dispatchDraw(Canvas canvas) {\n        super.dispatchDraw(canvas);\n        // Draw the margin drawable if needed.\n        mViewBehind.drawShadow(mContent, canvas);\n        mViewBehind.drawFade(mContent, canvas, getPercentOpen());\n        mViewBehind.drawSelector(mContent, canvas, getPercentOpen());\n    }\n\n    // variables for drawing\n    private float mScrollX = 0.0f;\n\n    private void onSecondaryPointerUp(MotionEvent ev) {\n        if (DEBUG) Log.v(TAG, \"onSecondaryPointerUp called\");\n        final int pointerIndex = MotionEventCompat.getActionIndex(ev);\n        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);\n        if (pointerId == mActivePointerId) {\n            // This was our active pointer going up. Choose a new\n            // active pointer and adjust accordingly.\n            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;\n            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);\n            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);\n            if (mVelocityTracker != null) {\n                mVelocityTracker.clear();\n            }\n        }\n    }\n\n    private void startDrag() {\n        mIsBeingDragged = true;\n        mQuickReturn = false;\n    }\n\n    private void endDrag() {\n        mQuickReturn = false;\n        mIsBeingDragged = false;\n        mIsUnableToDrag = false;\n        mActivePointerId = INVALID_POINTER;\n\n        if (mVelocityTracker != null) {\n            mVelocityTracker.recycle();\n            mVelocityTracker = null;\n        }\n    }\n\n    private void setScrollingCacheEnabled(boolean enabled) {\n        if (mScrollingCacheEnabled != enabled) {\n            mScrollingCacheEnabled = enabled;\n            if (USE_CACHE) {\n                final int size = getChildCount();\n                for (int i = 0; i < size; ++i) {\n                    final View child = getChildAt(i);\n                    if (child.getVisibility() != GONE) {\n                        child.setDrawingCacheEnabled(enabled);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Tests scrollability within child views of v given a delta of dx.\n     *\n     * @param v      View to test for horizontal scrollability\n     * @param checkV Whether the view v passed should itself be checked for scrollability (true),\n     *               or just its children (false).\n     * @param dx     Delta scrolled in pixels\n     * @param x      X coordinate of the active touch point\n     * @param y      Y coordinate of the active touch point\n     * @return true if child views of v can be scrolled by delta of dx.\n     */\n    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {\n        if (v instanceof ViewGroup) {\n            final ViewGroup group = (ViewGroup) v;\n            final int scrollX = v.getScrollX();\n            final int scrollY = v.getScrollY();\n            final int count = group.getChildCount();\n            // Count backwards - let topmost views consume scroll distance first.\n            for (int i = count - 1; i >= 0; i--) {\n                final View child = group.getChildAt(i);\n                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\n                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\n                        canScroll(child, true, dx, x + scrollX - child.getLeft(),\n                                y + scrollY - child.getTop())) {\n                    return true;\n                }\n            }\n        }\n\n        return checkV && ViewCompat.canScrollHorizontally(v, -dx);\n    }\n\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n        // Let the focused view and/or our descendants get the key first\n        return super.dispatchKeyEvent(event) || executeKeyEvent(event);\n    }\n\n    /**\n     * You can call this function yourself to have the scroll view perform\n     * scrolling from a key event, just as if the event had been dispatched to\n     * it by the view hierarchy.\n     *\n     * @param event The key event to execute.\n     * @return Return true if the event was handled, else false.\n     */\n    public boolean executeKeyEvent(KeyEvent event) {\n        boolean handled = false;\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            switch (event.getKeyCode()) {\n                case KeyEvent.KEYCODE_DPAD_LEFT:\n                    handled = arrowScroll(FOCUS_LEFT);\n                    break;\n                case KeyEvent.KEYCODE_DPAD_RIGHT:\n                    handled = arrowScroll(FOCUS_RIGHT);\n                    break;\n                case KeyEvent.KEYCODE_TAB:\n                    if (Build.VERSION.SDK_INT >= 11) {\n                        // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD\n                        // before Android 3.0. Ignore the tab key on those devices.\n                        if (event.hasNoModifiers()) {\n                            handled = arrowScroll(FOCUS_FORWARD);\n                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {\n                            handled = arrowScroll(FOCUS_BACKWARD);\n                        }\n                    }\n                    break;\n            }\n        }\n        return handled;\n    }\n\n    public boolean arrowScroll(int direction) {\n        View currentFocused = findFocus();\n        if (currentFocused == this) currentFocused = null;\n\n        boolean handled = false;\n\n        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,\n                direction);\n        if (nextFocused != null && nextFocused != currentFocused) {\n            if (direction == View.FOCUS_LEFT) {\n                handled = nextFocused.requestFocus();\n            } else if (direction == View.FOCUS_RIGHT) {\n                // If there is nothing to the right, or this is causing us to\n                // jump to the left, then what we really want to do is page right.\n                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {\n                    handled = pageRight();\n                } else {\n                    handled = nextFocused.requestFocus();\n                }\n            }\n        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {\n            // Trying to move left and nothing there; try to page.\n            handled = pageLeft();\n        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {\n            // Trying to move right and nothing there; try to page.\n            handled = pageRight();\n        }\n        if (handled) {\n            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));\n        }\n        return handled;\n    }\n\n    boolean pageLeft() {\n        if (mCurItem > 0) {\n            setCurrentItem(mCurItem - 1, true);\n            return true;\n        }\n        return false;\n    }\n\n    boolean pageRight() {\n        if (mCurItem < 1) {\n            setCurrentItem(mCurItem + 1, true);\n            return true;\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/java/com/jeremyfeinstein/slidingmenu/lib/CustomViewBehind.java",
    "content": "package com.jeremyfeinstein.slidingmenu.lib;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.util.TypedValue;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.CanvasTransformer;\n\npublic class CustomViewBehind extends ViewGroup {\n\n    private static final String TAG = \"CustomViewBehind\";\n\n    private static final int MARGIN_THRESHOLD = 48; // dips\n    private int mTouchMode = SlidingMenu.TOUCHMODE_MARGIN;\n\n    private CustomViewAbove mViewAbove;\n\n    private View mContent;\n    private View mSecondaryContent;\n    private int mMarginThreshold;\n    private int mWidthOffset;\n    private CanvasTransformer mTransformer;\n    private boolean mChildrenEnabled;\n\n    public CustomViewBehind(Context context) {\n        this(context, null);\n    }\n\n    public CustomViewBehind(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mMarginThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,\n                MARGIN_THRESHOLD, getResources().getDisplayMetrics());\n    }\n\n    public void setCustomViewAbove(CustomViewAbove customViewAbove) {\n        mViewAbove = customViewAbove;\n    }\n\n    public void setCanvasTransformer(CanvasTransformer t) {\n        mTransformer = t;\n    }\n\n    public void setWidthOffset(int i) {\n        mWidthOffset = i;\n        requestLayout();\n    }\n\n    public void setMarginThreshold(int marginThreshold) {\n        mMarginThreshold = marginThreshold;\n    }\n\n    public int getMarginThreshold() {\n        return mMarginThreshold;\n    }\n\n    public int getBehindWidth() {\n        return mContent.getWidth();\n    }\n\n    public void setContent(View v) {\n        if (mContent != null)\n            removeView(mContent);\n        mContent = v;\n        addView(mContent);\n    }\n\n    public View getContent() {\n        return mContent;\n    }\n\n    /**\n     * Sets the secondary (right) menu for use when setMode is called with SlidingMenu.LEFT_RIGHT.\n     *\n     * @param v the right menu\n     */\n    public void setSecondaryContent(View v) {\n        if (mSecondaryContent != null)\n            removeView(mSecondaryContent);\n        mSecondaryContent = v;\n        addView(mSecondaryContent);\n    }\n\n    public View getSecondaryContent() {\n        return mSecondaryContent;\n    }\n\n    public void setChildrenEnabled(boolean enabled) {\n        mChildrenEnabled = enabled;\n    }\n\n    @Override\n    public void scrollTo(int x, int y) {\n        super.scrollTo(x, y);\n        if (mTransformer != null)\n            invalidate();\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent e) {\n        return !mChildrenEnabled;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent e) {\n        return !mChildrenEnabled;\n    }\n\n    @Override\n    protected void dispatchDraw(Canvas canvas) {\n        if (mTransformer != null) {\n            canvas.save();\n            mTransformer.transformCanvas(canvas, mViewAbove.getPercentOpen());\n            super.dispatchDraw(canvas);\n            canvas.restore();\n        } else\n            super.dispatchDraw(canvas);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        final int width = r - l;\n        final int height = b - t;\n        mContent.layout(0, 0, width - mWidthOffset, height);\n        if (mSecondaryContent != null)\n            mSecondaryContent.layout(0, 0, width - mWidthOffset, height);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = getDefaultSize(0, widthMeasureSpec);\n        int height = getDefaultSize(0, heightMeasureSpec);\n        setMeasuredDimension(width, height);\n        final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width - mWidthOffset);\n        final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);\n        mContent.measure(contentWidth, contentHeight);\n        if (mSecondaryContent != null)\n            mSecondaryContent.measure(contentWidth, contentHeight);\n    }\n\n    private int mMode;\n    private boolean mFadeEnabled;\n    private final Paint mFadePaint = new Paint();\n    private float mScrollScale;\n    private Drawable mShadowDrawable;\n    private Drawable mSecondaryShadowDrawable;\n    private int mShadowWidth;\n    private float mFadeDegree;\n\n    public void setMode(int mode) {\n        if (mode == SlidingMenu.LEFT || mode == SlidingMenu.RIGHT) {\n            if (mContent != null)\n                mContent.setVisibility(View.VISIBLE);\n            if (mSecondaryContent != null)\n                mSecondaryContent.setVisibility(View.INVISIBLE);\n        }\n        mMode = mode;\n    }\n\n    public int getMode() {\n        return mMode;\n    }\n\n    public void setScrollScale(float scrollScale) {\n        mScrollScale = scrollScale;\n    }\n\n    public float getScrollScale() {\n        return mScrollScale;\n    }\n\n    public void setShadowDrawable(Drawable shadow) {\n        mShadowDrawable = shadow;\n        invalidate();\n    }\n\n    public void setSecondaryShadowDrawable(Drawable shadow) {\n        mSecondaryShadowDrawable = shadow;\n        invalidate();\n    }\n\n    public void setShadowWidth(int width) {\n        mShadowWidth = width;\n        invalidate();\n    }\n\n    public void setFadeEnabled(boolean b) {\n        mFadeEnabled = b;\n    }\n\n    public void setFadeDegree(float degree) {\n        if (degree > 1.0f || degree < 0.0f)\n            throw new IllegalStateException(\"The BehindFadeDegree must be between 0.0f and 1.0f\");\n        mFadeDegree = degree;\n    }\n\n    public int getMenuPage(int page) {\n        page = (page > 1) ? 2 : ((page < 1) ? 0 : page);\n        if (mMode == SlidingMenu.LEFT && page > 1) {\n            return 0;\n        } else if (mMode == SlidingMenu.RIGHT && page < 1) {\n            return 2;\n        } else {\n            return page;\n        }\n    }\n\n    public void scrollBehindTo(View content, int x, int y) {\n        int vis = View.VISIBLE;\n        if (mMode == SlidingMenu.LEFT) {\n            if (x >= content.getLeft()) vis = View.INVISIBLE;\n            scrollTo((int) ((x + getBehindWidth()) * mScrollScale), y);\n        } else if (mMode == SlidingMenu.RIGHT) {\n            if (x <= content.getLeft()) vis = View.INVISIBLE;\n            scrollTo((int) (getBehindWidth() - getWidth() +\n                    (x - getBehindWidth()) * mScrollScale), y);\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            mContent.setVisibility(x >= content.getLeft() ? View.INVISIBLE : View.VISIBLE);\n            mSecondaryContent.setVisibility(x <= content.getLeft() ? View.INVISIBLE : View.VISIBLE);\n            vis = x == 0 ? View.INVISIBLE : View.VISIBLE;\n            if (x <= content.getLeft()) {\n                scrollTo((int) ((x + getBehindWidth()) * mScrollScale), y);\n            } else {\n                scrollTo((int) (getBehindWidth() - getWidth() +\n                        (x - getBehindWidth()) * mScrollScale), y);\n            }\n        }\n        if (vis == View.INVISIBLE)\n            Log.v(TAG, \"behind INVISIBLE\");\n        setVisibility(vis);\n    }\n\n    public int getMenuLeft(View content, int page) {\n        if (mMode == SlidingMenu.LEFT) {\n            switch (page) {\n                case 0:\n                    return content.getLeft() - getBehindWidth();\n                case 2:\n                    return content.getLeft();\n            }\n        } else if (mMode == SlidingMenu.RIGHT) {\n            switch (page) {\n                case 0:\n                    return content.getLeft();\n                case 2:\n                    return content.getLeft() + getBehindWidth();\n            }\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            switch (page) {\n                case 0:\n                    return content.getLeft() - getBehindWidth();\n                case 2:\n                    return content.getLeft() + getBehindWidth();\n            }\n        }\n        return content.getLeft();\n    }\n\n    public int getAbsLeftBound(View content) {\n        if (mMode == SlidingMenu.LEFT || mMode == SlidingMenu.LEFT_RIGHT) {\n            return content.getLeft() - getBehindWidth();\n        } else if (mMode == SlidingMenu.RIGHT) {\n            return content.getLeft();\n        }\n        return 0;\n    }\n\n    public int getAbsRightBound(View content) {\n        if (mMode == SlidingMenu.LEFT) {\n            return content.getLeft();\n        } else if (mMode == SlidingMenu.RIGHT || mMode == SlidingMenu.LEFT_RIGHT) {\n            return content.getLeft() + getBehindWidth();\n        }\n        return 0;\n    }\n\n    public boolean marginTouchAllowed(View content, int x) {\n        int left = content.getLeft();\n        int right = content.getRight();\n        if (mMode == SlidingMenu.LEFT) {\n            return (x >= left && x <= mMarginThreshold + left);\n        } else if (mMode == SlidingMenu.RIGHT) {\n            return (x <= right && x >= right - mMarginThreshold);\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            return (x >= left && x <= mMarginThreshold + left) ||\n                    (x <= right && x >= right - mMarginThreshold);\n        }\n        return false;\n    }\n\n    public void setTouchMode(int i) {\n        mTouchMode = i;\n    }\n\n    public boolean menuOpenTouchAllowed(View content, int currPage, float x) {\n        switch (mTouchMode) {\n            case SlidingMenu.TOUCHMODE_FULLSCREEN:\n                return true;\n            case SlidingMenu.TOUCHMODE_MARGIN:\n                return menuTouchInQuickReturn(content, currPage, x);\n        }\n        return false;\n    }\n\n    public boolean menuTouchInQuickReturn(View content, int currPage, float x) {\n        if (mMode == SlidingMenu.LEFT || (mMode == SlidingMenu.LEFT_RIGHT && currPage == 0)) {\n            return x >= content.getLeft();\n        } else if (mMode == SlidingMenu.RIGHT || (mMode == SlidingMenu.LEFT_RIGHT && currPage == 2)) {\n            return x <= content.getRight();\n        }\n        return false;\n    }\n\n    public boolean menuClosedSlideAllowed(float dx) {\n        if (mMode == SlidingMenu.LEFT) {\n            return dx > 0;\n        } else if (mMode == SlidingMenu.RIGHT) {\n            return dx < 0;\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            return true;\n        }\n        return false;\n    }\n\n    public boolean menuOpenSlideAllowed(float dx) {\n        if (mMode == SlidingMenu.LEFT) {\n            return dx < 0;\n        } else if (mMode == SlidingMenu.RIGHT) {\n            return dx > 0;\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            return true;\n        }\n        return false;\n    }\n\n    public void drawShadow(View content, Canvas canvas) {\n        if (mShadowDrawable == null || mShadowWidth <= 0) return;\n        int left = 0;\n        if (mMode == SlidingMenu.LEFT) {\n            left = content.getLeft() - mShadowWidth;\n        } else if (mMode == SlidingMenu.RIGHT) {\n            left = content.getRight();\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            if (mSecondaryShadowDrawable != null) {\n                left = content.getRight();\n                mSecondaryShadowDrawable.setBounds(left, 0, left + mShadowWidth, getHeight());\n                mSecondaryShadowDrawable.draw(canvas);\n            }\n            left = content.getLeft() - mShadowWidth;\n        }\n        mShadowDrawable.setBounds(left, 0, left + mShadowWidth, getHeight());\n        mShadowDrawable.draw(canvas);\n    }\n\n    public void drawFade(View content, Canvas canvas, float openPercent) {\n        if (!mFadeEnabled) return;\n        final int alpha = (int) (mFadeDegree * 255 * Math.abs(1 - openPercent));\n        mFadePaint.setColor(Color.argb(alpha, 0, 0, 0));\n        int left = 0;\n        int right = 0;\n        if (mMode == SlidingMenu.LEFT) {\n            left = content.getLeft() - getBehindWidth();\n            right = content.getLeft();\n        } else if (mMode == SlidingMenu.RIGHT) {\n            left = content.getRight();\n            right = content.getRight() + getBehindWidth();\n        } else if (mMode == SlidingMenu.LEFT_RIGHT) {\n            left = content.getLeft() - getBehindWidth();\n            right = content.getLeft();\n            canvas.drawRect(left, 0, right, getHeight(), mFadePaint);\n            left = content.getRight();\n            right = content.getRight() + getBehindWidth();\n        }\n        canvas.drawRect(left, 0, right, getHeight(), mFadePaint);\n    }\n\n    private boolean mSelectorEnabled = true;\n    private Bitmap mSelectorDrawable;\n    private View mSelectedView;\n\n    public void drawSelector(View content, Canvas canvas, float openPercent) {\n        if (!mSelectorEnabled) return;\n        if (mSelectorDrawable != null && mSelectedView != null) {\n            String tag = (String) mSelectedView.getTag(R.id.selected_view);\n            if (tag.equals(TAG + \"SelectedView\")) {\n                canvas.save();\n                int left, right, offset;\n                offset = (int) (mSelectorDrawable.getWidth() * openPercent);\n                if (mMode == SlidingMenu.LEFT) {\n                    right = content.getLeft();\n                    left = right - offset;\n                    canvas.clipRect(left, 0, right, getHeight());\n                    canvas.drawBitmap(mSelectorDrawable, left, getSelectorTop(), null);\n                } else if (mMode == SlidingMenu.RIGHT) {\n                    left = content.getRight();\n                    right = left + offset;\n                    canvas.clipRect(left, 0, right, getHeight());\n                    canvas.drawBitmap(mSelectorDrawable, right - mSelectorDrawable.getWidth(), getSelectorTop(), null);\n                }\n                canvas.restore();\n            }\n        }\n    }\n\n    public void setSelectorEnabled(boolean b) {\n        mSelectorEnabled = b;\n    }\n\n    public void setSelectedView(View v) {\n        if (mSelectedView != null) {\n            mSelectedView.setTag(R.id.selected_view, null);\n            mSelectedView = null;\n        }\n        if (v != null && v.getParent() != null) {\n            mSelectedView = v;\n            mSelectedView.setTag(R.id.selected_view, TAG + \"SelectedView\");\n            invalidate();\n        }\n    }\n\n    private int getSelectorTop() {\n        int y = mSelectedView.getTop();\n        y += (mSelectedView.getHeight() - mSelectorDrawable.getHeight()) / 2;\n        return y;\n    }\n\n    public void setSelectorBitmap(Bitmap b) {\n        mSelectorDrawable = b;\n        refreshDrawableState();\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/java/com/jeremyfeinstein/slidingmenu/lib/SlidingMenu.java",
    "content": "package com.jeremyfeinstein.slidingmenu.lib;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Canvas;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.widget.FrameLayout;\nimport android.widget.RelativeLayout;\n\nimport com.jeremyfeinstein.slidingmenu.lib.CustomViewAbove.OnPageChangeListener;\n\nimport java.lang.reflect.Method;\n\npublic class SlidingMenu extends RelativeLayout {\n\n    private static final String TAG = SlidingMenu.class.getSimpleName();\n\n    public static final int SLIDING_WINDOW = 0;\n    public static final int SLIDING_CONTENT = 1;\n    private boolean mActionbarOverlay = false;\n\n    /**\n     * Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe\n     * gesture on the screen's margin\n     */\n    public static final int TOUCHMODE_MARGIN = 0;\n\n    /**\n     * Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe\n     * gesture anywhere on the screen\n     */\n    public static final int TOUCHMODE_FULLSCREEN = 1;\n\n    /**\n     * Constant value for use with setTouchModeAbove(). Denies the SlidingMenu to be opened with a swipe\n     * gesture\n     */\n    public static final int TOUCHMODE_NONE = 2;\n\n    /**\n     * Constant value for use with setMode(). Puts the menu to the left of the content.\n     */\n    public static final int LEFT = 0;\n\n    /**\n     * Constant value for use with setMode(). Puts the menu to the right of the content.\n     */\n    public static final int RIGHT = 1;\n\n    /**\n     * Constant value for use with setMode(). Puts menus to the left and right of the content.\n     */\n    public static final int LEFT_RIGHT = 2;\n\n    private CustomViewAbove mViewAbove;\n\n    private CustomViewBehind mViewBehind;\n\n    private OnOpenListener mOpenListener;\n\n    private OnOpenListener mSecondaryOpenListner;\n\n    private OnCloseListener mCloseListener;\n\n    /**\n     * The listener interface for receiving onOpen events.\n     * The class that is interested in processing a onOpen\n     * event implements this interface, and the object created\n     * with that class is registered with a component using the\n     * component's <code>addOnOpenListener<code> method. When\n     * the onOpen event occurs, that object's appropriate\n     * method is invoked\n     */\n    public interface OnOpenListener {\n\n        /**\n         * On open.\n         */\n        public void onOpen();\n    }\n\n    /**\n     * The listener interface for receiving onOpened events.\n     * The class that is interested in processing a onOpened\n     * event implements this interface, and the object created\n     * with that class is registered with a component using the\n     * component's <code>addOnOpenedListener<code> method. When\n     * the onOpened event occurs, that object's appropriate\n     * method is invoked.\n     *\n     * @see OnOpenedEvent\n     */\n    public interface OnOpenedListener {\n\n        /**\n         * On opened.\n         */\n        public void onOpened();\n    }\n\n    /**\n     * The listener interface for receiving onClose events.\n     * The class that is interested in processing a onClose\n     * event implements this interface, and the object created\n     * with that class is registered with a component using the\n     * component's <code>addOnCloseListener<code> method. When\n     * the onClose event occurs, that object's appropriate\n     * method is invoked.\n     *\n     * @see OnCloseEvent\n     */\n    public interface OnCloseListener {\n\n        /**\n         * On close.\n         */\n        public void onClose();\n    }\n\n    /**\n     * The listener interface for receiving onClosed events.\n     * The class that is interested in processing a onClosed\n     * event implements this interface, and the object created\n     * with that class is registered with a component using the\n     * component's <code>addOnClosedListener<code> method. When\n     * the onClosed event occurs, that object's appropriate\n     * method is invoked.\n     *\n     * @see OnClosedEvent\n     */\n    public interface OnClosedListener {\n\n        /**\n         * On closed.\n         */\n        public void onClosed();\n    }\n\n    /**\n     * The Interface CanvasTransformer.\n     */\n    public interface CanvasTransformer {\n\n        /**\n         * Transform canvas.\n         *\n         * @param canvas      the canvas\n         * @param percentOpen the percent open\n         */\n        public void transformCanvas(Canvas canvas, float percentOpen);\n    }\n\n    /**\n     * Instantiates a new SlidingMenu.\n     *\n     * @param context the associated Context\n     */\n    public SlidingMenu(Context context) {\n        this(context, null);\n    }\n\n    /**\n     * Instantiates a new SlidingMenu and attach to Activity.\n     *\n     * @param activity   the activity to attach slidingmenu\n     * @param slideStyle the slidingmenu style\n     */\n    public SlidingMenu(Activity activity, int slideStyle) {\n        this(activity, null);\n        this.attachToActivity(activity, slideStyle);\n    }\n\n    /**\n     * Instantiates a new SlidingMenu.\n     *\n     * @param context the associated Context\n     * @param attrs   the attrs\n     */\n    public SlidingMenu(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    /**\n     * Instantiates a new SlidingMenu.\n     *\n     * @param context  the associated Context\n     * @param attrs    the attrs\n     * @param defStyle the def style\n     */\n    public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        LayoutParams behindParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n        mViewBehind = new CustomViewBehind(context);\n        addView(mViewBehind, behindParams);\n        LayoutParams aboveParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);\n        mViewAbove = new CustomViewAbove(context);\n        addView(mViewAbove, aboveParams);\n        // register the CustomViewBehind with the CustomViewAbove\n        mViewAbove.setCustomViewBehind(mViewBehind);\n        mViewBehind.setCustomViewAbove(mViewAbove);\n        mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {\n            public static final int POSITION_OPEN = 0;\n            public static final int POSITION_CLOSE = 1;\n            public static final int POSITION_SECONDARY_OPEN = 2;\n\n            public void onPageScrolled(int position, float positionOffset,\n                                       int positionOffsetPixels) {\n            }\n\n            public void onPageSelected(int position) {\n                if (position == POSITION_OPEN && mOpenListener != null) {\n                    mOpenListener.onOpen();\n                } else if (position == POSITION_CLOSE && mCloseListener != null) {\n                    mCloseListener.onClose();\n                } else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null) {\n                    mSecondaryOpenListner.onOpen();\n                }\n            }\n        });\n\n        // now style everything!\n        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);\n        // set the above and behind views if defined in xml\n        int mode = ta.getInt(R.styleable.SlidingMenu_mode, LEFT);\n        setMode(mode);\n        int viewAbove = ta.getResourceId(R.styleable.SlidingMenu_viewAbove, -1);\n        if (viewAbove != -1) {\n            setContent(viewAbove);\n        } else {\n            setContent(new FrameLayout(context));\n        }\n        int viewBehind = ta.getResourceId(R.styleable.SlidingMenu_viewBehind, -1);\n        if (viewBehind != -1) {\n            setMenu(viewBehind);\n        } else {\n            setMenu(new FrameLayout(context));\n        }\n        int touchModeAbove = ta.getInt(R.styleable.SlidingMenu_touchModeAbove, TOUCHMODE_MARGIN);\n        setTouchModeAbove(touchModeAbove);\n        int touchModeBehind = ta.getInt(R.styleable.SlidingMenu_touchModeBehind, TOUCHMODE_MARGIN);\n        setTouchModeBehind(touchModeBehind);\n\n        int offsetBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindOffset, -1);\n        int widthBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindWidth, -1);\n        if (offsetBehind != -1 && widthBehind != -1)\n            throw new IllegalStateException(\"Cannot set both behindOffset and behindWidth for a SlidingMenu\");\n        else if (offsetBehind != -1)\n            setBehindOffset(offsetBehind);\n        else if (widthBehind != -1)\n            setBehindWidth(widthBehind);\n        else\n            setBehindOffset(0);\n        float scrollOffsetBehind = ta.getFloat(R.styleable.SlidingMenu_behindScrollScale, 0.33f);\n        setBehindScrollScale(scrollOffsetBehind);\n        int shadowRes = ta.getResourceId(R.styleable.SlidingMenu_shadowDrawable, -1);\n        if (shadowRes != -1) {\n            setShadowDrawable(shadowRes);\n        }\n        int shadowWidth = (int) ta.getDimension(R.styleable.SlidingMenu_shadowWidth, 0);\n        setShadowWidth(shadowWidth);\n        boolean fadeEnabled = ta.getBoolean(R.styleable.SlidingMenu_fadeEnabled, true);\n        setFadeEnabled(fadeEnabled);\n        float fadeDeg = ta.getFloat(R.styleable.SlidingMenu_fadeDegree, 0.33f);\n        setFadeDegree(fadeDeg);\n        boolean selectorEnabled = ta.getBoolean(R.styleable.SlidingMenu_selectorEnabled, false);\n        setSelectorEnabled(selectorEnabled);\n        int selectorRes = ta.getResourceId(R.styleable.SlidingMenu_selectorDrawable, -1);\n        if (selectorRes != -1)\n            setSelectorDrawable(selectorRes);\n        ta.recycle();\n    }\n\n    /**\n     * Attaches the SlidingMenu to an entire Activity\n     *\n     * @param activity   the Activity\n     * @param slideStyle either SLIDING_CONTENT or SLIDING_WINDOW\n     */\n    public void attachToActivity(Activity activity, int slideStyle) {\n        attachToActivity(activity, slideStyle, false);\n    }\n\n    /**\n     * Attaches the SlidingMenu to an entire Activity\n     *\n     * @param activity         the Activity\n     * @param slideStyle       either SLIDING_CONTENT or SLIDING_WINDOW\n     * @param actionbarOverlay whether or not the ActionBar is overlaid\n     */\n    public void attachToActivity(Activity activity, int slideStyle, boolean actionbarOverlay) {\n        if (slideStyle != SLIDING_WINDOW && slideStyle != SLIDING_CONTENT)\n            throw new IllegalArgumentException(\"slideStyle must be either SLIDING_WINDOW or SLIDING_CONTENT\");\n\n        if (getParent() != null)\n            throw new IllegalStateException(\"This SlidingMenu appears to already be attached\");\n\n        // get the window background\n        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{android.R.attr.windowBackground});\n        int background = a.getResourceId(0, 0);\n        a.recycle();\n\n        switch (slideStyle) {\n            case SLIDING_WINDOW:\n                mActionbarOverlay = false;\n                ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();\n                ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);\n                // save ActionBar themes that have transparent assets\n                decorChild.setBackgroundResource(background);\n                decor.removeView(decorChild);\n                decor.addView(this);\n                setContent(decorChild);\n                break;\n            case SLIDING_CONTENT:\n                mActionbarOverlay = actionbarOverlay;\n                // take the above view out of\n                ViewGroup contentParent = (ViewGroup) activity.findViewById(android.R.id.content);\n                View content = contentParent.getChildAt(0);\n                contentParent.removeView(content);\n                contentParent.addView(this);\n                setContent(content);\n                // save people from having transparent backgrounds\n                if (content.getBackground() == null)\n                    content.setBackgroundResource(background);\n                break;\n        }\n    }\n\n    /**\n     * Set the above view content from a layout resource. The resource will be inflated, adding all top-level views\n     * to the above view.\n     *\n     * @param res the new content\n     */\n    public void setContent(int res) {\n        setContent(LayoutInflater.from(getContext()).inflate(res, null));\n    }\n\n    /**\n     * Set the above view content to the given View.\n     *\n     * @param view The desired content to display.\n     */\n    public void setContent(View view) {\n        mViewAbove.setContent(view);\n        showContent();\n    }\n\n    /**\n     * Retrieves the current content.\n     *\n     * @return the current content\n     */\n    public View getContent() {\n        return mViewAbove.getContent();\n    }\n\n    /**\n     * Set the behind view (menu) content from a layout resource. The resource will be inflated, adding all top-level views\n     * to the behind view.\n     *\n     * @param res the new content\n     */\n    public void setMenu(int res) {\n        setMenu(LayoutInflater.from(getContext()).inflate(res, null));\n    }\n\n    /**\n     * Set the behind view (menu) content to the given View.\n     *\n     * @param view The desired content to display.\n     */\n    public void setMenu(View v) {\n        mViewBehind.setContent(v);\n    }\n\n    /**\n     * Retrieves the layout_main_above menu.\n     *\n     * @return the layout_main_above menu\n     */\n    public View getMenu() {\n        return mViewBehind.getContent();\n    }\n\n    /**\n     * Set the secondary behind view (right menu) content from a layout resource. The resource will be inflated, adding all top-level views\n     * to the behind view.\n     *\n     * @param res the new content\n     */\n    public void setSecondaryMenu(int res) {\n        setSecondaryMenu(LayoutInflater.from(getContext()).inflate(res, null));\n    }\n\n    /**\n     * Set the secondary behind view (right menu) content to the given View.\n     *\n     * @param view The desired content to display.\n     */\n    public void setSecondaryMenu(View v) {\n        mViewBehind.setSecondaryContent(v);\n        //\t\tmViewBehind.invalidate();\n    }\n\n    /**\n     * Retrieves the current secondary menu (right).\n     *\n     * @return the current menu\n     */\n    public View getSecondaryMenu() {\n        return mViewBehind.getSecondaryContent();\n    }\n\n\n    /**\n     * Sets the sliding enabled.\n     *\n     * @param b true to enable sliding, false to disable it.\n     */\n    public void setSlidingEnabled(boolean b) {\n        mViewAbove.setSlidingEnabled(b);\n    }\n\n    /**\n     * Checks if is sliding enabled.\n     *\n     * @return true, if is sliding enabled\n     */\n    public boolean isSlidingEnabled() {\n        return mViewAbove.isSlidingEnabled();\n    }\n\n    /**\n     * Sets which side the SlidingMenu should appear on.\n     *\n     * @param mode must be either SlidingMenu.LEFT or SlidingMenu.RIGHT\n     */\n    public void setMode(int mode) {\n        if (mode != LEFT && mode != RIGHT && mode != LEFT_RIGHT) {\n            throw new IllegalStateException(\"SlidingMenu mode must be LEFT, RIGHT, or LEFT_RIGHT\");\n        }\n        mViewBehind.setMode(mode);\n    }\n\n    /**\n     * Returns the current side that the SlidingMenu is on.\n     *\n     * @return the current mode, either SlidingMenu.LEFT or SlidingMenu.RIGHT\n     */\n    public int getMode() {\n        return mViewBehind.getMode();\n    }\n\n    /**\n     * Sets whether or not the SlidingMenu is in static mode (i.e. nothing is moving and everything is showing)\n     *\n     * @param b true to set static mode, false to disable static mode.\n     */\n    public void setStatic(boolean b) {\n        if (b) {\n            setSlidingEnabled(false);\n            mViewAbove.setCustomViewBehind(null);\n            mViewAbove.setCurrentItem(1);\n            //\t\t\tmViewBehind.setCurrentItem(0);\n        } else {\n            mViewAbove.setCurrentItem(1);\n            //\t\t\tmViewBehind.setCurrentItem(1);\n            mViewAbove.setCustomViewBehind(mViewBehind);\n            setSlidingEnabled(true);\n        }\n    }\n\n    /**\n     * Opens the menu and shows the menu view.\n     */\n    public void showMenu() {\n        showMenu(true);\n    }\n\n    /**\n     * Opens the menu and shows the menu view.\n     *\n     * @param animate true to animate the transition, false to ignore animation\n     */\n    public void showMenu(boolean animate) {\n        mViewAbove.setCurrentItem(0, animate);\n    }\n\n    /**\n     * Opens the menu and shows the secondary menu view. Will default to the regular menu\n     * if there is only one.\n     */\n    public void showSecondaryMenu() {\n        showSecondaryMenu(true);\n    }\n\n    /**\n     * Opens the menu and shows the secondary (right) menu view. Will default to the regular menu\n     * if there is only one.\n     *\n     * @param animate true to animate the transition, false to ignore animation\n     */\n    public void showSecondaryMenu(boolean animate) {\n        mViewAbove.setCurrentItem(2, animate);\n    }\n\n    /**\n     * Closes the menu and shows the above view.\n     */\n    public void showContent() {\n        showContent(true);\n    }\n\n    /**\n     * Closes the menu and shows the above view.\n     *\n     * @param animate true to animate the transition, false to ignore animation\n     */\n    public void showContent(boolean animate) {\n        mViewAbove.setCurrentItem(1, animate);\n    }\n\n    /**\n     * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.\n     */\n    public void toggle() {\n        toggle(true);\n    }\n\n    /**\n     * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.\n     *\n     * @param animate true to animate the transition, false to ignore animation\n     */\n    public void toggle(boolean animate) {\n        if (isMenuShowing()) {\n            showContent(animate);\n        } else {\n            showMenu(animate);\n        }\n    }\n\n    /**\n     * Checks if is the behind view showing.\n     *\n     * @return Whether or not the behind view is showing\n     */\n    public boolean isMenuShowing() {\n        return mViewAbove.getCurrentItem() == 0 || mViewAbove.getCurrentItem() == 2;\n    }\n\n    /**\n     * Checks if is the behind view showing.\n     *\n     * @return Whether or not the behind view is showing\n     */\n    public boolean isSecondaryMenuShowing() {\n        return mViewAbove.getCurrentItem() == 2;\n    }\n\n    /**\n     * Gets the behind offset.\n     *\n     * @return The margin on the right of the screen that the behind view scrolls to\n     */\n    public int getBehindOffset() {\n        return ((LayoutParams) mViewBehind.getLayoutParams()).rightMargin;\n    }\n\n    /**\n     * Sets the behind offset.\n     *\n     * @param i The margin, in pixels, on the right of the screen that the behind view scrolls to.\n     */\n    public void setBehindOffset(int i) {\n        //\t\tRelativeLayout.LayoutParams params = ((RelativeLayout.LayoutParams)mViewBehind.getLayoutParams());\n        //\t\tint bottom = params.bottomMargin;\n        //\t\tint top = params.topMargin;\n        //\t\tint left = params.leftMargin;\n        //\t\tparams.setMargins(left, top, i, bottom);\n        mViewBehind.setWidthOffset(i);\n    }\n\n    /**\n     * Sets the behind offset.\n     *\n     * @param resID The dimension resource id to be set as the behind offset.\n     *              The menu, when open, will leave this width margin on the right of the screen.\n     */\n    public void setBehindOffsetRes(int resID) {\n        int i = (int) getContext().getResources().getDimension(resID);\n        setBehindOffset(i);\n    }\n\n    /**\n     * Sets the above offset.\n     *\n     * @param i the new above offset, in pixels\n     */\n    public void setAboveOffset(int i) {\n        mViewAbove.setAboveOffset(i);\n    }\n\n    /**\n     * Sets the above offset.\n     *\n     * @param resID The dimension resource id to be set as the above offset.\n     */\n    public void setAboveOffsetRes(int resID) {\n        int i = (int) getContext().getResources().getDimension(resID);\n        setAboveOffset(i);\n    }\n\n    /**\n     * Sets the behind width.\n     *\n     * @param i The width the Sliding Menu will open to, in pixels\n     */\n    @SuppressWarnings(\"deprecation\")\n    public void setBehindWidth(int i) {\n        int width;\n        Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))\n                .getDefaultDisplay();\n        try {\n            Class<?> cls = Display.class;\n            Class<?>[] parameterTypes = {Point.class};\n            Point parameter = new Point();\n            Method method = cls.getMethod(\"getSize\", parameterTypes);\n            method.invoke(display, parameter);\n            width = parameter.x;\n        } catch (Exception e) {\n            width = display.getWidth();\n        }\n        setBehindOffset(width - i);\n    }\n\n    /**\n     * Sets the behind width.\n     *\n     * @param res The dimension resource id to be set as the behind width offset.\n     *            The menu, when open, will open this wide.\n     */\n    public void setBehindWidthRes(int res) {\n        int i = (int) getContext().getResources().getDimension(res);\n        setBehindWidth(i);\n    }\n\n    /**\n     * Gets the behind scroll scale.\n     *\n     * @return The scale of the parallax scroll\n     */\n    public float getBehindScrollScale() {\n        return mViewBehind.getScrollScale();\n    }\n\n    /**\n     * Gets the touch mode margin threshold\n     *\n     * @return the touch mode margin threshold\n     */\n    public int getTouchmodeMarginThreshold() {\n        return mViewBehind.getMarginThreshold();\n    }\n\n    /**\n     * Set the touch mode margin threshold\n     *\n     * @param touchmodeMarginThreshold\n     */\n    public void setTouchmodeMarginThreshold(int touchmodeMarginThreshold) {\n        mViewBehind.setMarginThreshold(touchmodeMarginThreshold);\n    }\n\n    /**\n     * Sets the behind scroll scale.\n     *\n     * @param f The scale of the parallax scroll (i.e. 1.0f scrolls 1 pixel for every\n     *          1 pixel that the above view scrolls and 0.0f scrolls 0 pixels)\n     */\n    public void setBehindScrollScale(float f) {\n        if (f < 0 && f > 1)\n            throw new IllegalStateException(\"ScrollScale must be between 0 and 1\");\n        mViewBehind.setScrollScale(f);\n    }\n\n    /**\n     * Sets the behind canvas transformer.\n     *\n     * @param t the new behind canvas transformer\n     */\n    public void setBehindCanvasTransformer(CanvasTransformer t) {\n        mViewBehind.setCanvasTransformer(t);\n    }\n\n    /**\n     * Gets the touch mode above.\n     *\n     * @return the touch mode above\n     */\n    public int getTouchModeAbove() {\n        return mViewAbove.getTouchMode();\n    }\n\n    /**\n     * Controls whether the SlidingMenu can be opened with a swipe gesture.\n     * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN}, {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN},\n     * or {@link #TOUCHMODE_NONE TOUCHMODE_NONE}\n     *\n     * @param i the new touch mode\n     */\n    public void setTouchModeAbove(int i) {\n        if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN\n                && i != TOUCHMODE_NONE) {\n            throw new IllegalStateException(\"TouchMode must be set to either\" +\n                    \"TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.\");\n        }\n        mViewAbove.setTouchMode(i);\n    }\n\n    /**\n     * Controls whether the SlidingMenu can be opened with a swipe gesture.\n     * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN}, {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN},\n     * or {@link #TOUCHMODE_NONE TOUCHMODE_NONE}\n     *\n     * @param i the new touch mode\n     */\n    public void setTouchModeBehind(int i) {\n        if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN\n                && i != TOUCHMODE_NONE) {\n            throw new IllegalStateException(\"TouchMode must be set to either\" +\n                    \"TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.\");\n        }\n        mViewBehind.setTouchMode(i);\n    }\n\n    /**\n     * Sets the shadow drawable.\n     *\n     * @param resId the resource ID of the new shadow drawable\n     */\n    public void setShadowDrawable(int resId) {\n        setShadowDrawable(getContext().getResources().getDrawable(resId));\n    }\n\n    /**\n     * Sets the shadow drawable.\n     *\n     * @param d the new shadow drawable\n     */\n    public void setShadowDrawable(Drawable d) {\n        mViewBehind.setShadowDrawable(d);\n    }\n\n    /**\n     * Sets the secondary (right) shadow drawable.\n     *\n     * @param resId the resource ID of the new shadow drawable\n     */\n    public void setSecondaryShadowDrawable(int resId) {\n        setSecondaryShadowDrawable(getContext().getResources().getDrawable(resId));\n    }\n\n    /**\n     * Sets the secondary (right) shadow drawable.\n     *\n     * @param d the new shadow drawable\n     */\n    public void setSecondaryShadowDrawable(Drawable d) {\n        mViewBehind.setSecondaryShadowDrawable(d);\n    }\n\n    /**\n     * Sets the shadow width.\n     *\n     * @param resId The dimension resource id to be set as the shadow width.\n     */\n    public void setShadowWidthRes(int resId) {\n        setShadowWidth((int) getResources().getDimension(resId));\n    }\n\n    /**\n     * Sets the shadow width.\n     *\n     * @param pixels the new shadow width, in pixels\n     */\n    public void setShadowWidth(int pixels) {\n        mViewBehind.setShadowWidth(pixels);\n    }\n\n    /**\n     * Enables or disables the SlidingMenu's fade in and out\n     *\n     * @param b true to enable fade, false to disable it\n     */\n    public void setFadeEnabled(boolean b) {\n        mViewBehind.setFadeEnabled(b);\n    }\n\n    /**\n     * Sets how much the SlidingMenu fades in and out. Fade must be enabled, see\n     * {@link #setFadeEnabled(boolean) setFadeEnabled(boolean)}\n     *\n     * @param f the new fade degree, between 0.0f and 1.0f\n     */\n    public void setFadeDegree(float f) {\n        mViewBehind.setFadeDegree(f);\n    }\n\n    /**\n     * Enables or disables whether the selector is drawn\n     *\n     * @param b true to draw the selector, false to not draw the selector\n     */\n    public void setSelectorEnabled(boolean b) {\n        mViewBehind.setSelectorEnabled(true);\n    }\n\n    /**\n     * Sets the selected view. The selector will be drawn here\n     *\n     * @param v the new selected view\n     */\n    public void setSelectedView(View v) {\n        mViewBehind.setSelectedView(v);\n    }\n\n    /**\n     * Sets the selector drawable.\n     *\n     * @param res a resource ID for the selector drawable\n     */\n    public void setSelectorDrawable(int res) {\n        mViewBehind.setSelectorBitmap(BitmapFactory.decodeResource(getResources(), res));\n    }\n\n    /**\n     * Sets the selector drawable.\n     *\n     * @param b the new selector bitmap\n     */\n    public void setSelectorBitmap(Bitmap b) {\n        mViewBehind.setSelectorBitmap(b);\n    }\n\n    /**\n     * Add a View ignored by the Touch Down event when mode is Fullscreen\n     *\n     * @param v a view to be ignored\n     */\n    public void addIgnoredView(View v) {\n        mViewAbove.addIgnoredView(v);\n    }\n\n    /**\n     * Remove a View ignored by the Touch Down event when mode is Fullscreen\n     *\n     * @param v a view not wanted to be ignored anymore\n     */\n    public void removeIgnoredView(View v) {\n        mViewAbove.removeIgnoredView(v);\n    }\n\n    /**\n     * Clear the list of Views ignored by the Touch Down event when mode is Fullscreen\n     */\n    public void clearIgnoredViews() {\n        mViewAbove.clearIgnoredViews();\n    }\n\n    /**\n     * Sets the OnOpenListener. {@link OnOpenListener#onOpen() OnOpenListener.onOpen()} will be called when the SlidingMenu is opened\n     *\n     * @param listener the new OnOpenListener\n     */\n    public void setOnOpenListener(OnOpenListener listener) {\n        //mViewAbove.setOnOpenListener(listener);\n        mOpenListener = listener;\n    }\n\n\n    /**\n     * Sets the OnOpenListner for secondary menu  {@link OnOpenListener#onOpen() OnOpenListener.onOpen()} will be called when the secondary SlidingMenu is opened\n     *\n     * @param listener the new OnOpenListener\n     */\n\n    public void setSecondaryOnOpenListner(OnOpenListener listener) {\n        mSecondaryOpenListner = listener;\n    }\n\n    /**\n     * Sets the OnCloseListener. {@link OnCloseListener#onClose() OnCloseListener.onClose()} will be called when any one of the SlidingMenu is closed\n     *\n     * @param listener the new setOnCloseListener\n     */\n    public void setOnCloseListener(OnCloseListener listener) {\n        //mViewAbove.setOnCloseListener(listener);\n        mCloseListener = listener;\n    }\n\n    /**\n     * Sets the OnOpenedListener. {@link OnOpenedListener#onOpened() OnOpenedListener.onOpened()} will be called after the SlidingMenu is opened\n     *\n     * @param listener the new OnOpenedListener\n     */\n    public void setOnOpenedListener(OnOpenedListener listener) {\n        mViewAbove.setOnOpenedListener(listener);\n    }\n\n    /**\n     * Sets the OnClosedListener. {@link OnClosedListener#onClosed() OnClosedListener.onClosed()} will be called after the SlidingMenu is closed\n     *\n     * @param listener the new OnClosedListener\n     */\n    public void setOnClosedListener(OnClosedListener listener) {\n        mViewAbove.setOnClosedListener(listener);\n    }\n\n    public static class SavedState extends BaseSavedState {\n\n        private final int mItem;\n\n        public SavedState(Parcelable superState, int item) {\n            super(superState);\n            mItem = item;\n        }\n\n        private SavedState(Parcel in) {\n            super(in);\n            mItem = in.readInt();\n        }\n\n        public int getItem() {\n            return mItem;\n        }\n\n        /* (non-Javadoc)\n         * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)\n         */\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeInt(mItem);\n        }\n\n        public static final Creator<SavedState> CREATOR =\n                new Creator<SavedState>() {\n                    public SavedState createFromParcel(Parcel in) {\n                        return new SavedState(in);\n                    }\n\n                    public SavedState[] newArray(int size) {\n                        return new SavedState[size];\n                    }\n                };\n\n    }\n\n    /* (non-Javadoc)\n     * @see android.view.View#onSaveInstanceState()\n     */\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        Parcelable superState = super.onSaveInstanceState();\n        SavedState ss = new SavedState(superState, mViewAbove.getCurrentItem());\n        return ss;\n    }\n\n    /* (non-Javadoc)\n     * @see android.view.View#onRestoreInstanceState(android.os.Parcelable)\n     */\n    @Override\n    protected void onRestoreInstanceState(Parcelable state) {\n        SavedState ss = (SavedState) state;\n        super.onRestoreInstanceState(ss.getSuperState());\n        mViewAbove.setCurrentItem(ss.getItem());\n    }\n\n    /* (non-Javadoc)\n     * @see android.view.ViewGroup#fitSystemWindows(android.graphics.Rect)\n     */\n    @SuppressLint(\"NewApi\")\n    @Override\n    protected boolean fitSystemWindows(Rect insets) {\n        int leftPadding = insets.left;\n        int rightPadding = insets.right;\n        int topPadding = insets.top;\n        int bottomPadding = insets.bottom;\n        if (!mActionbarOverlay) {\n            Log.v(TAG, \"setting padding!\");\n            setPadding(leftPadding, topPadding, rightPadding, bottomPadding);\n        }\n        return true;\n    }\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public void manageLayers(float percentOpen) {\n        if (Build.VERSION.SDK_INT < 11) return;\n\n        boolean layer = percentOpen > 0.0f && percentOpen < 1.0f;\n        final int layerType = layer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;\n\n        if (layerType != getContent().getLayerType()) {\n            getHandler().post(new Runnable() {\n                public void run() {\n                    Log.v(TAG, \"changing layerType. hardware? \" + (layerType == View.LAYER_TYPE_HARDWARE));\n                    getContent().setLayerType(layerType, null);\n                    getMenu().setLayerType(layerType, null);\n                    if (getSecondaryMenu() != null) {\n                        getSecondaryMenu().setLayerType(layerType, null);\n                    }\n                }\n            });\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/res/values/attrs.xml",
    "content": "<resources>\n\n    <declare-styleable name=\"SlidingMenu\">\n        <attr name=\"mode\">\n            <enum name=\"left\" value=\"0\" />\n            <enum name=\"right\" value=\"1\" />\n        </attr>\n        <attr name=\"viewAbove\" format=\"reference\" />\n        <attr name=\"viewBehind\" format=\"reference\" />\n        <attr name=\"behindOffset\" format=\"dimension\" />\n        <attr name=\"behindWidth\" format=\"dimension\" />\n        <attr name=\"behindScrollScale\" format=\"float\" />\n        <attr name=\"touchModeAbove\">\n            <enum name=\"margin\" value=\"0\" />\n            <enum name=\"fullscreen\" value=\"1\" />\n            <enum name=\"none\" value=\"2\" />\n        </attr>\n        <attr name=\"touchModeBehind\">\n            <enum name=\"margin\" value=\"0\" />\n            <enum name=\"fullscreen\" value=\"1\" />\n            <enum name=\"none\" value=\"2\" />\n        </attr>\n        <attr name=\"shadowDrawable\" format=\"reference\" />\n        <attr name=\"shadowWidth\" format=\"dimension\" />\n        <attr name=\"fadeEnabled\" format=\"boolean\" />\n        <attr name=\"fadeDegree\" format=\"float\" />\n        <attr name=\"selectorEnabled\" format=\"boolean\" />\n        <attr name=\"selectorDrawable\" format=\"reference\" />\n    </declare-styleable>\n\n</resources>"
  },
  {
    "path": "projects/sample/source/sample-plugin/third-party/slidingmenu/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"selected_view\" type=\"id\" />\n\n</resources>"
  },
  {
    "path": "projects/sdk/coding/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.gradletasknamecache"
  },
  {
    "path": "projects/sdk/coding/aar-to-jar-plugin/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/aar-to-jar-plugin/build.gradle",
    "content": "apply plugin: 'java-gradle-plugin'\n\napply plugin: 'kotlin'\n\ngradlePlugin {\n    plugins {\n        shadow {\n            id = \"com.tencent.shadow.internal.aar-to-jar\"\n            implementationClass = \"com.tencent.shadow.coding.aar_to_jar_plugin.AarToJarPlugin\"\n        }\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"com.android.tools.build:gradle:$build_gradle_version\"\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation gradleTestKit()\n\n}\n"
  },
  {
    "path": "projects/sdk/coding/aar-to-jar-plugin/src/main/kotlin/com/tencent/shadow/coding/aar_to_jar_plugin/AarToJarPlugin.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.coding.aar_to_jar_plugin\n\nimport com.android.build.gradle.BaseExtension\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.tasks.Copy\nimport java.util.*\n\nclass AarToJarPlugin : Plugin<Project> {\n\n    override fun apply(project: Project) {\n        project.afterEvaluate {\n            val android = it.extensions.getByName(\"android\") as BaseExtension\n            android.buildTypes.forEach { buildType ->\n                val createJarPackageTask = createJarPackageTask(project, buildType.name)\n                addJarConfiguration(project, buildType.name, createJarPackageTask)\n            }\n        }\n    }\n\n    private fun createJarPackageTask(project: Project, buildType: String): Task {\n        val taskName = \"jar${buildType.capitalize(Locale.getDefault())}Package\"\n        return project.tasks.create(taskName, Copy::class.java) {\n            fun buildDirFile(relativePath: String) =\n                project.file(project.buildDir.path + relativePath)\n\n            val aarFileName = \"${project.name}-${buildType}\"\n            val aarFile = buildDirFile(\"/outputs/aar/${aarFileName}.aar\")\n            val outputDir = buildDirFile(\"/outputs/jar\")\n\n            it.from(project.zipTree(aarFile))\n            it.into(outputDir)\n            it.include(\"classes.jar\")\n            it.rename(\"classes.jar\", \"${aarFileName}.jar\")\n            it.group = \"build\"\n            it.description = \"生成jar包\"\n        }.dependsOn(\n            project.getTasksByName(\n                \"assemble${buildType.capitalize(Locale.getDefault())}\",\n                false\n            ).first()\n        )\n    }\n\n    /**\n     * 添加一个额外的Configuration，用于buildScript中以classpath方式依赖\n     */\n    private fun addJarConfiguration(\n        project: Project,\n        buildType: String,\n        createJarPackageTask: Task\n    ) {\n        val configurationName = \"jar-${buildType}\"\n        val jarFile =\n            project.file(project.buildDir.path + \"/outputs/jar/${project.name}-${buildType}.jar\")\n        project.configurations.create(configurationName)\n        project.artifacts.add(configurationName, jarFile) {\n            it.builtBy(createJarPackageTask)\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/coding/android-jar/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/android-jar/build.gradle",
    "content": "apply plugin: 'java-library'\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n}\n\nevaluationDependsOn(':get-android-jar')\ndependencies {\n    def androidJarPath = project(':get-android-jar').androidJarPath\n    api files(androidJarPath)\n}\n"
  },
  {
    "path": "projects/sdk/coding/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\n//buildscript不能从其他gradle文件中apply，所以这段buildscript脚本存在于多个子构建中。\n//请更新buildscript时同步更新。\nbuildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\napply from: '../../../buildScripts/gradle/common.gradle'\n\nallprojects {\n    group 'com.tencent.shadow.coding'\n}\n\ntasks.create('test').dependsOn subprojects.collect { it.getTasksByName('test', false) }\n\n"
  },
  {
    "path": "projects/sdk/coding/code-generator/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/code-generator/build.gradle",
    "content": "apply plugin: 'kotlin'\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"com.squareup:javapoet:$javapoet_version\"\n    implementation \"org.javassist:javassist:$javassist_version\"\n    implementation \"junit:junit:$junit_version\"\n\n    compileOnly project(':android-jar')\n    testImplementation project(':android-jar')\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n"
  },
  {
    "path": "projects/sdk/coding/code-generator/src/main/kotlin/com/tencent/shadow/coding/code_generator/ActivityCodeGenerator.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.tencent.shadow.coding.code_generator\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.app.Application\nimport android.app.NativeActivity\nimport android.content.ComponentCallbacks2\nimport android.view.ContextThemeWrapper\nimport android.view.KeyEvent\nimport android.view.Window\nimport com.squareup.javapoet.AnnotationSpec\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.CodeBlock\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.TypeName\nimport com.squareup.javapoet.TypeSpec\nimport com.squareup.javapoet.TypeVariableName\nimport com.tencent.shadow.core.runtime.NeighborClass\nimport javassist.ClassMap\nimport javassist.ClassPool\nimport javassist.LoaderClassPath\nimport javassist.bytecode.Descriptor\nimport java.io.File\nimport java.lang.reflect.Method\nimport java.lang.reflect.Type\nimport javax.lang.model.element.Modifier\n\n/**\n * Activity相关代码生成逻辑\n *\n * 这个逻辑应该可以进一步抽象成任意组件的相关代码生成逻辑，不局限于Activity\n *\n * 下面解释相关代码指的是什么。\n *\n * 假设业务有Activity名为BizActivity。那么原本BizActivity extends Activity。\n * 在Shadow的方案中，也就是代理的方案中，需要宿主中有一个PluginContainerActivity，\n * PluginContainerActivity extends Activity。然后将BizActivity改为\n * BizActivity extends ShadowActivity。其中ShadowActivity extends PluginActivity。\n * 然后让PluginContainerActivity implements HostActivityDelegator接口，\n * 让Loader中的ShadowActivityDelegate implements HostActivityDelegate接口。\n *\n * 然后我们要将Activity上的方法分成3类：\n * 1.私有方法，BizActivity原本也不会访问到这些方法，这些方法我们不用管。\n *\n * 2.系统会调用的方法，如onCreate等生命周期回调，这些方法需要由PluginContainerActivity\n * 转调给HostActivityDelegate，HostActivityDelegate再转调给ShadowActivity，\n * ShadowActivity再转调给HostActivityDelegator接口上定义的super前缀方法，\n * super前缀方法实现为调用原本的super相应方法。这样一来，系统调用到PluginContainerActivity\n * 上的方法，就可以被BizActivity响应，并且控制何时调用super方法。\n *\n * 3.插件会调用的方法，如setContentView，这些方法需要在ShadowActivity中\n * 声明出一样的方法，并且实现为调用HostActivityDelegator接口上的同名方法。以便\n * BizActivity在Override后通过super调用还能够调用到原本的Activity父类实现上。\n * 与\"系统会调用的方法\"相比，省去了super前缀方法转调，因为PluginContainerActivity\n * 不需要Override这些方法。\n *\n * 其中涉及被Shadow Transform修改了类型的方法，需要将相关代码的类型也修改掉。比如，\n * getParent方法的返回类型需要改为ShadowActivity。\n *\n */\nclass ActivityCodeGenerator {\n\n    companion object {\n        const val ACTIVITY_CONTAINER_PACKAGE = \"com.tencent.shadow.core.runtime.container\"\n        const val RUNTIME_PACKAGE = \"com.tencent.shadow.core.runtime\"\n        const val DELEGATE_PACKAGE = \"com.tencent.shadow.core.loader.delegates\"\n\n        const val PREFIX = \"Generated\"\n\n        //CS:const string\n        const val CS_HostActivityDelegate = \"${PREFIX}HostActivityDelegate\"\n        const val CS_HostActivityDelegator = \"${PREFIX}HostActivityDelegator\"\n        const val CS_PluginContainerActivity = \"${PREFIX}PluginContainerActivity\"\n        const val CS_NativePluginContainerActivity = \"${PREFIX}NativePluginContainerActivity\"\n        const val CS_PluginActivity = \"${PREFIX}PluginActivity\"\n        const val CS_ShadowActivityDelegate = \"${PREFIX}ShadowActivityDelegate\"\n        const val CS_delegate_field = \"hostActivityDelegate\"\n        const val CS_delegator_field = \"hostActivityDelegator\"\n        const val CS_pluginActivity_field = \"pluginActivity\"\n\n        val classPool = ClassPool.getDefault()\n\n        init {\n            // 兼容javassist升级后的ClassPool#appendSystemPath()改动：\n            // https://github.com/jboss-javassist/javassist/commit/e41e0790c0cb073e9e2e30071afecfcdc4621d42\n            if (javassist.bytecode.ClassFile.MAJOR_VERSION < javassist.bytecode.ClassFile.JAVA_9) {\n                val cl = Thread.currentThread().contextClassLoader\n                classPool.appendClassPath(LoaderClassPath(cl))\n            }\n            classPool.makeClass(\"$RUNTIME_PACKAGE.ShadowApplication\")\n                .toClass(NeighborClass::class.java)\n        }\n\n        val ActivityClass = Activity::class.java\n        val NativeActivityClass = NativeActivity::class.java\n        val ModifiedActivityClass = modifySdkClass(Activity::class.java)\n        val activityCallbackMethods = getActivityCallbackMethods(ActivityClass)\n        val otherMethods = getOtherMethods(ActivityClass)\n        val activityCallbackMethodsModified = getActivityCallbackMethods(ModifiedActivityClass)\n        val otherMethodsModified = getOtherMethods(ModifiedActivityClass)\n\n        /**\n         * 统一在这里修改SDK中的Class对象，替换其中的类型为Shadow Runtime的类型\n         */\n        fun modifySdkClass(clazz: Class<*>): Class<*> {\n            val name = clazz.name\n            val renameMap = ClassMap()\n            val ctClass = classPool.get(name)\n            val newClassNames = mutableListOf<String>()\n            ctClass.name = name\n\n            mapOf(\n                Activity::class to \"ShadowActivity\",\n                Application::class to \"ShadowApplication\"\n            ).forEach {\n                val newClassName = \"$RUNTIME_PACKAGE.${it.value}\"\n                renameMap[Descriptor.toJvmName(it.key.java.name)] =\n                    Descriptor.toJvmName(newClassName)\n\n                newClassNames.add(newClassName)\n            }\n\n            ctClass.replaceClassName(renameMap)\n            return ctClass.toClass(NeighborClass::class.java)\n        }\n\n        fun getActivityMethods(clazz: Class<*>): List<Method> {\n            val allMethods = clazz.methods.toMutableSet()\n            allMethods.addAll(clazz.declaredMethods)\n            return allMethods\n                .filter {\n                    it.declaringClass != Object::class.java\n                }\n        }\n\n        /**\n         * 有一部分方法系统会调用，插件也会调用。\n         * 这部分方法是否真的是这样，以后还是需要再仔细看一下。\n         * 先这样定义出来，叫做Custom也是因为暂时不知道叫什么好。\n         */\n        fun getCustomMethods(clazz: Class<*>): Set<Method> {\n            val set = mutableSetOf<Method>()\n\n            fun addMethod(name: String, vararg args: Class<*>) {\n                val method =\n                    try {\n                        clazz.getDeclaredMethod(name, * args)\n                    } catch (e: NoSuchMethodException) {\n                        clazz.getMethod(name, * args)\n                    }\n                set.add(method)\n            }\n\n            addMethod(\"isChangingConfigurations\")\n            addMethod(\"finish\")\n            addMethod(\"getClassLoader\")\n            addMethod(\"getLayoutInflater\")\n            addMethod(\"getResources\")\n            addMethod(\"recreate\")\n            addMethod(\"getCallingActivity\")\n\n            return set\n        }\n\n        fun getActivityCallbackMethods(clazz: Class<*>): Set<Method> {\n            val callbacks = mutableSetOf<Method>()\n\n            callbacks.addAll(getCustomMethods(clazz))\n\n            val startWithOnMethods = getActivityMethods(clazz)\n                .filter {\n                    java.lang.reflect.Modifier.isPublic(it.modifiers) or\n                            java.lang.reflect.Modifier.isProtected(it.modifiers)\n                }.filter {\n                    it.name.startsWith(\"on\")\n                }\n            callbacks.addAll(startWithOnMethods)\n\n            val callbackInterface = getActivityMethods(clazz).filter {\n                java.lang.reflect.Modifier.isPublic(it.modifiers) or\n                        java.lang.reflect.Modifier.isProtected(it.modifiers)\n            }.filter {\n                it.hasSameDefineIn(Window.Callback::class.java) or\n                        it.hasSameDefineIn(KeyEvent.Callback::class.java)\n            }\n            callbacks.addAll(callbackInterface)\n\n            return callbacks;\n        }\n\n        fun Method.hasSameDefineIn(clazz: Class<*>): Boolean {\n            return try {\n                clazz.getDeclaredMethod(name, *parameterTypes)\n                true\n            } catch (e: NoSuchMethodException) {\n                false\n            }\n        }\n\n        fun Method.hasSameMethodIn(clazz: Class<*>): Boolean {\n            return try {\n                try {\n                    clazz.getDeclaredMethod(name, *parameterTypes)\n                    true\n                } catch (e: NoSuchMethodException) {\n                    clazz.getMethod(name, *parameterTypes)\n                    true\n                }\n            } catch (e: NoSuchMethodException) {\n                false\n            }\n        }\n\n        fun getOtherMethods(clazz: Class<*>): Set<Method> {\n            val activityMethods = getActivityMethods(clazz)\n            val callbackMethods = getActivityCallbackMethods(clazz)\n            val filter = activityMethods.filterNot {\n                callbackMethods.contains(it)\n            }.filterNot {\n                (it.declaringClass != clazz) and\n                        java.lang.reflect.Modifier.isFinal(it.modifiers)\n            }\n\n            val result = mutableSetOf<Method>()\n            result.addAll(filter)\n            result.addAll(getCustomMethods(clazz))\n            return result\n        }\n\n        fun Method.toMethodSpecBuilder(prefix: String = \"\"): MethodSpec.Builder {\n            val methodName = if (prefix.isEmpty()) name else prefix + name.capitalize()\n            val builder = MethodSpec.methodBuilder(methodName)\n            parameters.forEach {\n                builder.addParameter(\n                    ParameterSpec.builder(it.parameterizedType, it.name).build()\n                )\n            }\n            builder.addExceptions(\n                exceptionTypes.map {\n                    TypeName.get(it)\n                }\n            )\n            builder.addTypeVariables(typeParameters.map {\n                TypeVariableName.get(it)\n            })\n            builder.returns(genericReturnType)\n            return builder\n        }\n\n        fun Method.toInterfaceMethodSpec(prefix: String = \"\"): MethodSpec {\n            val builder = toMethodSpecBuilder(prefix)\n            builder.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n            return builder.build()\n        }\n\n        /**\n         * 这个类集中解决一个问题的逻辑。问题Issue见https://github.com/Tencent/Shadow/issues/230\n         *\n         * 问题描述：\n         * 一般情况下，一个类的方法签名中如果含有找不到的类，在这个类本身被new出来时是不会出错的。\n         * JVM不会去检查一个类的所有方法的签名中涉及到的类是否都存在。只有当调用该方法时才会发现签名上的类找不到。\n         *\n         * 但是在com.tencent.shadow.core.loader.ShadowPluginLoader.getHostActivityDelegate中，\n         * 将实现以接口类型返回时，涉及一种特殊情况，就是实现和接口不是由同一个ClassLoader加载的。\n         * 在这种情况下，JVM会检查实现是否真的实现了接口的每一个方法定义。所以会去尝试加载接口上所有方法签名涉及的类。\n         * 这会导致在低版本系统上尝试加载高版本引入的类，比如android.app.role.RoleManager是API29引入的，\n         * 在API28的手机上就会出现LinkageError。\n         *\n         * 解决方案就是对于高版本API引入的类在生成代理转调相关代码时:在Delegate、ShadowActivityDelegate\n         * 两个生成类中使用Object类型替代原本类型，在PluginContainerActivity中调用Delegate方法时强制类型转换，\n         * 在ShadowActivityDelegate调用PluginActivity方法时也对参数进行强制类型转换。\n         *\n         * 这个方法中定义哪些类型对于低版本API是安全的，不需要采用上述方案。其余类型则采用上述方案。\n         */\n        private fun Class<*>.isSafeForLowApi(): Boolean {\n            val safeType: List<String?> = listOf(\n                android.app.Activity::class,\n                android.app.Dialog::class,\n                android.app.Fragment::class,\n                android.content.ComponentName::class,\n                android.content.Context::class,\n                android.content.Intent::class,\n                android.content.res.Configuration::class,\n                android.content.res.Resources.Theme::class,\n                android.content.res.Resources::class,\n                android.graphics.Bitmap::class,\n                android.graphics.Canvas::class,\n                android.net.Uri::class,\n                android.os.Bundle::class,\n                android.util.AttributeSet::class,\n                android.view.ActionMode.Callback::class,\n                android.view.ActionMode::class,\n                android.view.ContextMenu.ContextMenuInfo::class,\n                android.view.ContextMenu::class,\n                android.view.KeyEvent::class,\n                android.view.Menu::class,\n                android.view.MenuItem::class,\n                android.view.MotionEvent::class,\n                android.view.View::class,\n                android.view.WindowManager.LayoutParams::class,\n                android.view.accessibility.AccessibilityEvent::class,\n                android.view.LayoutInflater::class\n            ).map { it.java.canonicalName }\n\n            return isPrimitive or\n                    (isArray && componentType.isSafeForLowApi()) or\n                    (canonicalName.startsWith(\"java.lang\")) or\n                    safeType.contains(canonicalName)\n        }\n\n        fun Method.toMethodSpecBuilderWithObjectType(): MethodSpec.Builder {\n            val methodName = name\n            val builder = MethodSpec.methodBuilder(methodName)\n            parameters.forEach {\n                builder.addParameter(\n                    ParameterSpec.builder(\n                        if (it.type.isSafeForLowApi()) it.parameterizedType else Object::class.java,\n                        it.name\n                    )\n                        .build()\n                )\n            }\n            if (exceptionTypes.isNotEmpty()) {\n                builder.addExceptions(listOf(TypeName.get(Throwable::class.java)))\n            }\n            if (returnType.isSafeForLowApi()) {\n                builder.returns(returnType)\n            } else {\n                builder.returns(Object::class.java)\n            }\n            return builder\n        }\n\n        fun Method.toInterfaceMethodSpecWithObjectType(): MethodSpec {\n            val builder = toMethodSpecBuilderWithObjectType()\n            builder.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n            return builder.build()\n        }\n    }\n\n    val commonJavadoc =\n        \"由\\n\" + \"{@link com.tencent.shadow.coding.code_generator.ActivityCodeGenerator}\\n\" + \"自动生成\\n\"\n\n    val activityDelegate = defineActivityDelegate()\n    val activityDelegator = defineActivityDelegator()\n    val pluginContainerActivity = definePluginContainerActivity(\n        CS_PluginContainerActivity,\n        ActivityClass\n    )\n    val nativePluginContainerActivity = definePluginContainerActivity(\n        CS_NativePluginContainerActivity,\n        NativeActivityClass\n    )\n    val pluginActivity = definePluginActivity()\n    val shadowActivityDelegate = defineShadowActivityDelegate()\n\n    init {\n        addMethods()\n    }\n\n    fun defineActivityDelegate() =\n        TypeSpec.interfaceBuilder(CS_HostActivityDelegate)\n            .addModifiers(Modifier.PUBLIC)\n            .addJavadoc(\n                commonJavadoc\n                        + \"HostActivity的被委托者接口\\n\"\n                        + \"被委托者通过实现这个接口中声明的方法达到替代委托者实现的目的，从而将HostActivity的行为动态化。\\n\"\n            )\n\n    fun defineActivityDelegator() =\n        TypeSpec.interfaceBuilder(CS_HostActivityDelegator)\n            .addModifiers(Modifier.PUBLIC)\n            .addJavadoc(\n                commonJavadoc\n                        + \"HostActivityDelegator作为委托者的接口。主要提供它的委托方法的super方法，\\n\"\n                        + \"以便Delegate可以通过这个接口调用到Activity的super方法。\\n\"\n            )\n\n    fun definePluginContainerActivity(className: String, superclass: Type) =\n        TypeSpec.classBuilder(className)\n            .addModifiers(Modifier.ABSTRACT)\n            .superclass(superclass)\n            .addSuperinterface(ClassName.get(ACTIVITY_CONTAINER_PACKAGE, CS_HostActivityDelegator))\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressLint::class.java)\n                    .addMember(\"value\", \"{\\\"NewApi\\\", \\\"MissingPermission\\\"}\")\n                    .build()\n            )\n            .addField(\n                ClassName.get(ACTIVITY_CONTAINER_PACKAGE, CS_HostActivityDelegate),\n                CS_delegate_field\n            )\n            .addJavadoc(commonJavadoc)\n\n    fun definePluginActivity() =\n        TypeSpec.classBuilder(CS_PluginActivity)\n            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n            .superclass(ClassName.get(RUNTIME_PACKAGE, \"ShadowContext\"))\n            .addSuperinterfaces(\n                listOf(\n                    ClassName.get(ComponentCallbacks2::class.java),\n                    ClassName.get(Window.Callback::class.java),\n                    ClassName.get(KeyEvent.Callback::class.java)\n                )\n            )\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressLint::class.java)\n                    .addMember(\"value\", \"{\\\"NullableProblems\\\", \\\"deprecation\\\"}\")\n                    .build()\n            )\n            .addField(\n                ClassName.get(ACTIVITY_CONTAINER_PACKAGE, CS_HostActivityDelegator),\n                CS_delegator_field\n            )\n            .addJavadoc(commonJavadoc)\n\n    fun defineShadowActivityDelegate() =\n        TypeSpec.classBuilder(CS_ShadowActivityDelegate)\n            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n            .superclass(ClassName.get(DELEGATE_PACKAGE, \"ShadowDelegate\"))\n            .addSuperinterface(\n                ClassName.get(ACTIVITY_CONTAINER_PACKAGE, CS_HostActivityDelegate)\n            )\n            .addField(\n                ClassName.get(RUNTIME_PACKAGE, CS_PluginActivity),\n                CS_pluginActivity_field\n            )\n            .addJavadoc(commonJavadoc)\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressLint::class.java)\n                    .addMember(\"value\", \"\\\"NewApi\\\"\")\n                    .build()\n            )\n            .addAnnotation(\n                AnnotationSpec.builder(SuppressWarnings::class.java)\n                    .addMember(\"value\", \"{\\\"unchecked\\\", \\\"JavadocReference\\\"}\")\n                    .build()\n            )\n\n    fun generate(outputDir: File, moduleName: String) {\n        outputDir.mkdirs()\n\n        when (moduleName) {\n            \"activity_container\" -> {\n                listOf(\n                    activityDelegate,\n                    activityDelegator,\n                    pluginContainerActivity,\n                    nativePluginContainerActivity,\n                ).forEach {\n                    JavaFile.builder(ACTIVITY_CONTAINER_PACKAGE, it.build())\n                        .build().writeTo(outputDir)\n                }\n            }\n            \"runtime\" -> {\n                JavaFile.builder(RUNTIME_PACKAGE, pluginActivity.build())\n                    .build().writeTo(outputDir)\n            }\n            \"loader\" -> {\n                JavaFile.builder(DELEGATE_PACKAGE, shadowActivityDelegate.build())\n                    .build().writeTo(outputDir)\n            }\n            else -> throw IllegalArgumentException(\"非法的moduleName：$moduleName\")\n        }\n    }\n\n    fun addMethods() {\n        //将系统会调用的方法都定义出来，供转调之用。\n        activityDelegate.addMethods(activityCallbackMethods.map { it.toInterfaceMethodSpecWithObjectType() })\n\n        //将Activity可以被调用的方法都暴露出来\n        activityDelegator.addMethods(\n            otherMethods.map { it.toInterfaceMethodSpec() }\n        )\n\n        //TODO:这些方法应该不需要定义出来，但先对齐原手工实现的类，保证单元测试检测生成类和原手工写的类一致可以通过。\n        activityDelegator.addMethods(\n            activityCallbackMethods.filter {\n                it.hasSameDefineIn(Window.Callback::class.java)\n            }.map { it.toInterfaceMethodSpec() }\n        )\n\n        //添加系统会调用的方法的对应super方法，这些super方法实现时实现为调用super同名方法\n        activityDelegator.addMethods(\n            activityCallbackMethods.map { it.toInterfaceMethodSpec(\"super\") }\n        )\n\n        //TODO:这些方法并不需要添加super前缀方法，但先对齐原手工实现的类，保证单元测试检测生成类和原手工写的类一致可以通过。\n        activityDelegator.addMethods(\n            otherMethods.filterNot { activityCallbackMethods.contains(it) }\n                .map { it.toInterfaceMethodSpec(\"super\") }\n        )\n\n        //对系统会调用的方法转调到hostActivityDelegate去，再生成对应的super方法\n        listOf(pluginContainerActivity, nativePluginContainerActivity).forEach {\n            it.addMethods(\n                activityCallbackMethods.map(::delegateCallbackMethod)\n            )\n            it.addMethods(\n                activityCallbackMethods.map(::implementSuperMethod)\n            )\n            //TODO:这些方法并不需要添加super前缀方法，但先对齐原手工实现的类，保证单元测试检测生成类和原手工写的类一致可以通过。\n            it.addMethods(\n                otherMethods.filterNot { activityCallbackMethods.contains(it) }\n                    .map(::implementSuperMethod)\n            )\n\n            //将所有protected方法暴露成public方法\n            it.addMethods(\n                otherMethods.filter {\n                    java.lang.reflect.Modifier.isProtected(it.modifiers)\n                }.map(::exposeProtectedMethod)\n            )\n        }\n\n        pluginActivity.addMethods(\n            activityCallbackMethodsModified\n                .filterNot { it.name == \"getResources\" }\n                .filterNot { it.name == \"getClassLoader\" }\n                .map {\n                    defineMethodXXX(it, true)\n                }\n        )\n\n        pluginActivity.addMethods(\n            otherMethodsModified\n                .filterNot {\n                    it.hasSameMethodIn(ContextThemeWrapper::class.java)\n                }\n                .filterNot { getCustomMethods(ModifiedActivityClass).contains(it) }\n                .map {\n                    defineMethodXXX(it, false)\n                }\n                .toList()\n        )\n\n        //实现所有Delegate方法\n        shadowActivityDelegate.addMethods(\n            activityCallbackMethodsModified\n                .filter { it.isNotModified() }\n                .map {\n                    implementDelegateMethod(it)\n                }\n        )\n    }\n\n    //定义转调一个系统会调用的方法的实现\n    fun delegateCallbackMethod(method: Method): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilder()\n        if (java.lang.reflect.Modifier.isPublic(method.modifiers)) {\n            methodBuilder.addModifiers(Modifier.PUBLIC)\n        } else {\n            methodBuilder.addModifiers(Modifier.PROTECTED)\n        }\n        methodBuilder.addAnnotation(Override::class.java)\n\n        val ret = if (method.returnType == Void::class.javaPrimitiveType) \"\" else \"return \"\n\n        methodBuilder.beginControlFlow(\"if (${CS_delegate_field} != null)\")\n        val args = method.parameters.joinToString(separator = \", \") {\n            it.name\n        }\n        val retCast = if (method.returnType.isSafeForLowApi()) \"\" else \"(${method.returnType.name})\"\n        methodBuilder.addStatement(\"${ret}$retCast${CS_delegate_field}.${method.name}($args)\")\n        methodBuilder.nextControlFlow(\"else\")\n        methodBuilder.addStatement(\"${ret}super.${method.name}($args)\")\n        methodBuilder.endControlFlow()\n\n        return methodBuilder.build()\n    }\n\n    //实现super前缀方法\n    fun implementSuperMethod(method: Method): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilder(\"super\")\n        methodBuilder.addModifiers(Modifier.PUBLIC)\n        methodBuilder.addAnnotation(Override::class.java)\n\n        val ret = if (method.returnType == Void::class.javaPrimitiveType) \"\" else \"return \"\n        val args = method.parameters.joinToString(separator = \", \") {\n            it.name\n        }\n        methodBuilder.addStatement(\"${ret}super.${method.name}($args)\")\n\n        return methodBuilder.build()\n    }\n\n    //定义暴露protected的方法\n    fun exposeProtectedMethod(method: Method): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilder()\n        methodBuilder.addModifiers(Modifier.PUBLIC)\n\n        val ret = if (method.returnType == Void::class.javaPrimitiveType) \"\" else \"return \"\n        val args = method.parameters.joinToString(separator = \", \") {\n            it.name\n        }\n        methodBuilder.addStatement(\"${ret}super.${method.name}($args)\")\n\n        return methodBuilder.build()\n    }\n\n    //定义方法的实现\n    fun defineMethod(method: Method, hasSuperMethod: Boolean): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilder()\n        methodBuilder.addModifiers(Modifier.PUBLIC)\n\n        val ret = if (method.returnType == Void::class.javaPrimitiveType) \"\" else \"return \"\n        val args = method.parameters.joinToString(separator = \", \") {\n            it.name\n        }\n        val invokeMethod =\n            if (hasSuperMethod)\n                \"super${method.name.capitalize()}\"\n            else\n                \"${method.name}\"\n        methodBuilder.addStatement(\"${ret}${CS_delegator_field}.${invokeMethod}($args)\")\n\n        return methodBuilder.build()\n    }\n\n    fun defineAbstractMethod(method: Method): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilder()\n        methodBuilder.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)\n        return methodBuilder.build()\n    }\n\n    fun Method.isNotModified(): Boolean {\n        return try {\n            val m = ActivityClass.getDeclaredMethod(name, *parameterTypes)\n            m.returnType == returnType\n        } catch (e: NoSuchMethodException) {\n            try {\n                val m = ActivityClass.getMethod(name, *parameterTypes)\n                m.returnType == returnType\n            } catch (e: NoSuchMethodException) {\n                false\n            }\n        }\n    }\n\n    fun defineMethodXXX(method: Method, hasSuperMethod: Boolean) =\n        if (method.isNotModified()) {\n            defineMethod(method, hasSuperMethod)\n        } else {\n            defineAbstractMethod(method)\n        }\n\n    fun implementDelegateMethod(method: Method): MethodSpec {\n        val methodBuilder = method.toMethodSpecBuilderWithObjectType()\n        methodBuilder.addModifiers(Modifier.PUBLIC)\n        methodBuilder.addAnnotation(Override::class.java)\n\n        val ret = if (method.returnType == Void::class.javaPrimitiveType) \"\" else \"return \"\n        val args = method.parameters.joinToString(separator = \", \") {\n            (if (it.type.isSafeForLowApi()) CodeBlock.of(\n                \"\\$L\",\n                it.name\n            ) else CodeBlock.of(\"(\\$T) \\$L\", it.parameterizedType, it.name)).toString()\n        }\n        methodBuilder.addStatement(\"${ret}${CS_pluginActivity_field}.${method.name}($args)\")\n\n        return methodBuilder.build()\n    }\n}\n"
  },
  {
    "path": "projects/sdk/coding/code-generator/src/main/kotlin/com/tencent/shadow/core/runtime/NeighborClass.kt",
    "content": "package com.tencent.shadow.core.runtime\n\n/**\n * 为了兼容JDK 17和javassist\n * https://github.com/jboss-javassist/javassist/issues/369\n */\nclass NeighborClass"
  },
  {
    "path": "projects/sdk/coding/code-generator/src/test/kotlin/com/tencent/shadow/coding/code_generator/ActivityCodeGeneratorTest.kt",
    "content": "package com.tencent.shadow.coding.code_generator\n\nimport org.junit.Test\n\n\ninternal class ActivityCodeGeneratorTest {\n    @Test\n    fun testLoadAndroidClass() {\n        ActivityCodeGenerator.classPool.get(\"android.app.Activity\")\n    }\n}"
  },
  {
    "path": "projects/sdk/coding/common-jar-settings/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/common-jar-settings/build.gradle",
    "content": "import com.squareup.javapoet.FieldSpec\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.TypeSpec\n\nimport javax.lang.model.element.Modifier\n\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.squareup:javapoet:$javapoet_version\"\n    }\n}\n\napply plugin: 'java-gradle-plugin'\n\napply plugin: 'kotlin'\n\ngradlePlugin {\n    plugins {\n        shadow {\n            id = \"com.tencent.shadow.internal.common-jar-settings\"\n            implementationClass = \"com.tencent.shadow.coding.common_jar_settings.CommonJarSettingsPlugin\"\n        }\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"com.android.tools.build:gradle:$build_gradle_version\"\n    implementation \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation gradleTestKit()\n}\n\n\njava {\n    sourceSets {\n        main.java.srcDirs += 'build/generateCode'\n    }\n}\n\ndef generateCode = tasks.register('generateCode') {\n    def androidJarPath = project(':get-android-jar').androidJarPath\n    inputs.property('androidJarPath', androidJarPath)\n    outputs.dir(layout.buildDirectory.dir('generateCode'))\n            .withPropertyName('outputDir')\n    doLast {\n        def androidJarPathField\n                = FieldSpec.builder(String, \"ANDROID_JAR_PATH\", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)\n                .initializer('$S', androidJarPath).build()\n\n        def androidJarClass = TypeSpec.classBuilder(\"AndroidJar\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addField(androidJarPathField).build()\n\n        JavaFile.builder(\"com.tencent.shadow.coding.common_jar_settings\", androidJarClass)\n                .build().writeTo(new File(project.getBuildDir(), 'generateCode'))\n    }\n}\n\ncompileKotlin.dependsOn(generateCode)\n"
  },
  {
    "path": "projects/sdk/coding/common-jar-settings/src/main/kotlin/com/tencent/shadow/coding/common_jar_settings/CommonJarSettingsPlugin.kt",
    "content": "package com.tencent.shadow.coding.common_jar_settings;\n\n\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.plugins.JavaPluginExtension\nimport org.gradle.api.tasks.compile.JavaCompile\n\n@Suppress(\"unused\")\nclass CommonJarSettingsPlugin : Plugin<Project> {\n\n    override fun apply(project: Project) {\n        project.pluginManager.apply(\"java-library\")\n        val java = project.extensions.getByType(JavaPluginExtension::class.java)\n\n        java.sourceCompatibility = JavaVersion.VERSION_1_7\n        java.targetCompatibility = JavaVersion.VERSION_1_7\n\n        val androidJar = project.files(AndroidJar.ANDROID_JAR_PATH)\n        // 将android.jar设置为这些jar工程的bootclasspath，以便javac编译时使用的JDK标准库采用android平台的定义\n        project.tasks.withType(JavaCompile::class.java) {\n            it.options.bootstrapClasspath = androidJar\n        }\n\n        // IDE不会自动索引bootstrapClasspath，所以把bootstrapClasspath重复添加到compileOnly中\n        project.dependencies.add(\"compileOnly\", androidJar)\n    }\n}\n"
  },
  {
    "path": "projects/sdk/coding/get-android-jar/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/get-android-jar/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n}\n\ndef sdkPath = android.sdkDirectory\ndef androidJarPath = new File(sdkPath, \"platforms/${android.compileSdkVersion}/android.jar\")\n\nif (!androidJarPath.exists()) {\n    println(\"File $androidJarPath not exists!\")\n    throw new RuntimeException(\"Android SDK ${android.compileSdkVersion} 没有安装\")\n}\n\next.set('androidJarPath', androidJarPath)\n"
  },
  {
    "path": "projects/sdk/coding/get-android-jar/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.coding.get_android_jar\" />\n"
  },
  {
    "path": "projects/sdk/coding/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "projects/sdk/coding/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sdk/coding/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sdk/coding/java-build-config/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/coding/java-build-config/build.gradle",
    "content": "import com.squareup.javapoet.FieldSpec\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.TypeSpec\n\nimport javax.lang.model.element.Modifier\n\napply plugin: 'java'\n\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.squareup:javapoet:$javapoet_version\"\n    }\n}\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n\n    sourceSets {\n        main.java.srcDirs += 'build/generateCode'\n    }\n}\n\ndef generateCode = tasks.register('generateCode') {\n    inputs.property('VERSION_NAME', project.VERSION_NAME)\n    outputs.dir(layout.buildDirectory.dir('generateCode'))\n            .withPropertyName('outputDir')\n    doLast {\n        def versionNameField\n                = FieldSpec.builder(String, \"VERSION_NAME\", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)\n                .initializer('$S', project.VERSION_NAME).build()\n\n        def buildConfigClass = TypeSpec.classBuilder(\"BuildConfig\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addField(versionNameField).build()\n\n        JavaFile.builder(\"com.tencent.shadow.coding.java_build_config\", buildConfigClass)\n                .build().writeTo(new File(project.getBuildDir(), 'generateCode'))\n    }\n}\n\ncompileJava.dependsOn(generateCode)\n"
  },
  {
    "path": "projects/sdk/coding/settings.gradle",
    "content": "include 'code-generator'\ninclude 'aar-to-jar-plugin'\ninclude 'common-jar-settings'\ninclude 'get-android-jar'\ninclude 'android-jar'\ninclude 'java-build-config'\n"
  },
  {
    "path": "projects/sdk/core/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.gradletasknamecache\n\n"
  },
  {
    "path": "projects/sdk/core/activity-container/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/activity-container/build.gradle",
    "content": "import com.tencent.shadow.coding.code_generator.ActivityCodeGenerator\n\nbuildscript {\n    dependencies {\n        classpath 'com.tencent.shadow.coding:android-jar'\n        classpath 'com.tencent.shadow.coding:code-generator'\n    }\n}\n\napply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\njava {\n    sourceSets {\n        main.java.srcDirs += 'build/generated/sources/code-generator'\n    }\n}\n\ndef generateCode = tasks.register('generateCode') {\n    def outputDir = layout.buildDirectory.dir('generated/sources/code-generator')\n    outputs.dir(outputDir)\n            .withPropertyName('outputDir')\n    doLast {\n        ActivityCodeGenerator codeGenerator = new ActivityCodeGenerator()\n        codeGenerator.generate(outputDir.get().getAsFile(), \"activity_container\")\n    }\n}\n\ncompileJava.dependsOn(generateCode)\n\ndependencies {\n    implementation 'com.tencent.shadow.coding:java-build-config'\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/DelegateProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n/**\n * 宿主容器委托提供者\n * <p>\n * 负责提供宿主容器委托实现\n *\n * @author cubershi\n */\npublic interface DelegateProvider {\n    String LOADER_VERSION_KEY = \"LOADER_VERSION\";\n\n    String PROCESS_ID_KEY = \"PROCESS_ID_KEY\";\n\n    /**\n     * 获取与delegator相应的HostActivityDelegate\n     *\n     * @param delegator HostActivity委托者\n     * @return HostActivity被委托者\n     */\n    HostActivityDelegate getHostActivityDelegate(Class<? extends HostActivityDelegator> delegator);\n\n}\n\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/DelegateProviderHolder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n\nimport android.os.SystemClock;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * DelegateProvider依赖注入类\n * <p>\n * dynamic-pluginloader通过这个类实现将PluginLoader中的DelegateProvider实现注入到plugincontainer中。\n *\n * @author cubershi\n */\npublic class DelegateProviderHolder {\n    public static final String DEFAULT_KEY = \"DEFAULT_KEY\";\n    private static Map<String, DelegateProvider> delegateProviderMap = new HashMap<>();\n\n    /**\n     * 为了防止系统有一定概率出现进程号重启后一致的问题，我们使用开机时间作为进程号来判断进程是否重启\n     */\n    public static long sCustomPid;\n\n    static {\n        sCustomPid = SystemClock.elapsedRealtime();\n    }\n\n    public static void setDelegateProvider(String key, DelegateProvider delegateProvider) {\n        delegateProviderMap.put(key, delegateProvider);\n    }\n\n    public static DelegateProvider getDelegateProvider(String key) {\n        return delegateProviderMap.get(key);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/HostActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport android.app.Activity;\nimport android.view.Window;\n\n/**\n * 表示一个Activity是宿主程序中的Activity\n *\n * @author cubershi\n */\npublic interface HostActivity {\n    /**\n     * 返回Activity对象本身\n     *\n     * @return Activity对象本身\n     */\n    Activity getImplementActivity();\n\n    /**\n     * 返回Activity的Window\n     *\n     * @return Activity的Window\n     */\n    Window getImplementWindow();\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/HostActivityDelegate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n/**\n * HostActivity的被委托者接口\n * <p>\n * 被委托者通过实现这个接口中声明的方法达到替代委托者实现的目的，从而将HostActivity的行为动态化。\n *\n * @author cubershi\n */\npublic interface HostActivityDelegate extends GeneratedHostActivityDelegate {\n    void setDelegator(HostActivityDelegator delegator);\n\n    Object getPluginActivity();\n\n    String getLoaderVersion();\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/HostActivityDelegator.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n/**\n * HostActivity作为委托者的接口。主要提供它的委托方法的super方法，\n * 以便Delegate可以通过这个接口调用到Activity的super方法。\n * <p>\n * cubershi\n */\npublic interface HostActivityDelegator extends GeneratedHostActivityDelegator {\n    HostActivity getHostActivity();\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/HostNativeActivityDelegate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport android.content.pm.PackageManager;\nimport android.view.InputQueue;\nimport android.view.SurfaceHolder;\n\npublic interface HostNativeActivityDelegate extends HostActivityDelegate {\n\n    PackageManager getPackageManager();\n\n    void surfaceCreated(SurfaceHolder holder);\n\n    void surfaceChanged(SurfaceHolder holder, int format, int width, int height);\n\n    void surfaceRedrawNeeded(SurfaceHolder holder);\n\n    void surfaceDestroyed(SurfaceHolder holder);\n\n    void onInputQueueCreated(InputQueue queue);\n\n    void onInputQueueDestroyed(InputQueue queue);\n\n    void onGlobalLayout();\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/HostNativeActivityDelegator.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport android.view.InputQueue;\nimport android.view.SurfaceHolder;\n\npublic interface HostNativeActivityDelegator extends HostActivityDelegator {\n    void superSurfaceCreated(SurfaceHolder holder);\n\n    void superSurfaceChanged(SurfaceHolder holder, int format, int width, int height);\n\n    void superSurfaceRedrawNeeded(SurfaceHolder holder);\n\n    void superSurfaceDestroyed(SurfaceHolder holder);\n\n    void superOnInputQueueCreated(InputQueue queue);\n\n    void superOnInputQueueDestroyed(InputQueue queue);\n\n    void superOnGlobalLayout();\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/NativePluginContainerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport static com.tencent.shadow.core.runtime.container.DelegateProvider.LOADER_VERSION_KEY;\nimport static com.tencent.shadow.core.runtime.container.DelegateProvider.PROCESS_ID_KEY;\n\nimport android.app.Activity;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.InputQueue;\nimport android.view.SurfaceHolder;\nimport android.view.Window;\n\nimport com.tencent.shadow.coding.java_build_config.BuildConfig;\n\n/**\n * NativeActivity位于宿主中的壳子\n * <p>\n * TODO 和PluginContainerActivity重复代码过多，但由于是继承自不同父类，需要用JavaPoet生成代码来减少重复。\n */\npublic class NativePluginContainerActivity extends GeneratedNativePluginContainerActivity implements HostActivity, HostNativeActivityDelegator {\n    private static final String TAG = \"NativePluginConAct\";\n\n    HostNativeActivityDelegate hostActivityDelegate;\n\n    private boolean isBeforeOnCreate = true;\n\n    public NativePluginContainerActivity() {\n        HostNativeActivityDelegate delegate;\n        DelegateProvider delegateProvider = DelegateProviderHolder.getDelegateProvider(getDelegateProviderKey());\n        if (delegateProvider != null) {\n            delegate = (HostNativeActivityDelegate) delegateProvider.getHostActivityDelegate(this.getClass());\n            delegate.setDelegator(this);\n        } else {\n            Log.e(TAG, \"NativePluginContainerActivity: DelegateProviderHolder没有初始化\");\n            delegate = null;\n        }\n        super.hostActivityDelegate = delegate;\n        hostActivityDelegate = delegate;\n    }\n\n    protected String getDelegateProviderKey() {\n        return DelegateProviderHolder.DEFAULT_KEY;\n    }\n\n    final public Object getPluginActivity() {\n        if (hostActivityDelegate != null) {\n            return hostActivityDelegate.getPluginActivity();\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    final protected void onCreate(Bundle savedInstanceState) {\n        isBeforeOnCreate = false;\n        mHostTheme = null;//释放资源\n\n        boolean illegalIntent = isIllegalIntent(savedInstanceState);\n        if (illegalIntent) {\n            super.hostActivityDelegate = null;\n            hostActivityDelegate = null;\n            Log.e(TAG, \"illegalIntent savedInstanceState==\" + savedInstanceState + \" getIntent().getExtras()==\" + getIntent().getExtras());\n        }\n\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onCreate(savedInstanceState);\n        } else {\n            //这里是进程被杀后重启后走到，当需要恢复fragment状态的时候，由于系统保留了TAG，会因为找不到fragment引起crash\n            super.onCreate(null);\n            Log.e(TAG, \"onCreate: hostActivityDelegate==null finish activity\");\n            finish();\n            System.exit(0);\n        }\n    }\n\n    /**\n     * IllegalIntent指的是这些情况下的启动：\n     * 1.插件版本变化之后，残留于系统中的PendingIntent或系统因回收内存杀死进程残留的任务栈而启动。\n     * 由于插件版本变化，PluginLoader逻辑可能不一致，Intent中的参数可能不能满足新代码的启动条件。\n     * 2.外部的非法启动，无法确定一个插件的Activity。\n     * <p>\n     * <p>\n     * 3.不支持进程重启后莫名其妙的原因loader也加载了，但是可能要启动的plugin没有load，出现异常\n     *\n     * @param savedInstanceState onCreate时系统还回来的savedInstanceState\n     * @return <code>true</code>表示这次启动不是我们预料的，需要尽早finish并退出进程。\n     */\n    private boolean isIllegalIntent(Bundle savedInstanceState) {\n        Bundle extras = getIntent().getExtras();\n        if (extras == null && savedInstanceState == null) {\n            return true;\n        }\n        Bundle bundle;\n        bundle = savedInstanceState == null ? extras : savedInstanceState;\n        try {\n            String loaderVersion = bundle.getString(LOADER_VERSION_KEY);\n            long processVersion = bundle.getLong(PROCESS_ID_KEY);\n            return !BuildConfig.VERSION_NAME.equals(loaderVersion) || processVersion != DelegateProviderHolder.sCustomPid;\n        } catch (Throwable ignored) {\n            //捕获可能的非法Intent中包含我们根本反序列化不了的数据\n            return true;\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onSaveInstanceState(outState);\n        } else {\n            super.onSaveInstanceState(outState);\n        }\n        //避免插件setIntent清空掉LOADER_VERSION_KEY\n        outState.putString(LOADER_VERSION_KEY, BuildConfig.VERSION_NAME);\n        outState.putLong(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid);\n    }\n\n    @Override\n    public HostActivity getHostActivity() {\n        return this;\n    }\n\n    @Override\n    public Activity getImplementActivity() {\n        return this;\n    }\n\n    @Override\n    public Window getImplementWindow() {\n        return getWindow();\n    }\n\n    /**\n     * Theme一旦设置了就不能更换Theme所在的Resouces了，见{@link Resources.Theme#setTo(Resources.Theme)}\n     * 而Activity在OnCreate之前需要设置Theme和使用Theme。我们需要在Activity OnCreate之后才能注入插件资源。\n     * 这就需要在Activity OnCreate之前不要调用Activity的setTheme方法，同时在getTheme时返回宿主的Theme资源。\n     * 注：{@link Activity#setTheme(int)}会触发初始化Theme，因此不能调用。\n     */\n    private Resources.Theme mHostTheme;\n\n    @Override\n    public Resources.Theme getTheme() {\n        if (isBeforeOnCreate) {\n            if (mHostTheme == null) {\n                mHostTheme = super.getResources().newTheme();\n            }\n            return mHostTheme;\n        } else {\n            return super.getTheme();\n        }\n    }\n\n    @Override\n    public void setTheme(int resid) {\n        if (!isBeforeOnCreate) {\n            super.setTheme(resid);\n        }\n    }\n\n    @Override\n    public PackageManager getPackageManager() {\n        if (hostActivityDelegate != null) {\n            return hostActivityDelegate.getPackageManager();\n        } else {\n            return super.getPackageManager();\n        }\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.surfaceCreated(holder);\n        } else {\n            super.surfaceCreated(holder);\n        }\n    }\n\n    @Override\n    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.surfaceChanged(holder, format, width, height);\n        } else {\n            super.surfaceChanged(holder, format, width, height);\n        }\n    }\n\n    @Override\n    public void surfaceRedrawNeeded(SurfaceHolder holder) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.surfaceRedrawNeeded(holder);\n        } else {\n            super.surfaceRedrawNeeded(holder);\n        }\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.surfaceDestroyed(holder);\n        } else {\n            super.surfaceDestroyed(holder);\n        }\n    }\n\n    @Override\n    public void onInputQueueCreated(InputQueue queue) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onInputQueueCreated(queue);\n        } else {\n            super.onInputQueueCreated(queue);\n        }\n    }\n\n    @Override\n    public void onInputQueueDestroyed(InputQueue queue) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onInputQueueDestroyed(queue);\n        } else {\n            super.onInputQueueDestroyed(queue);\n        }\n    }\n\n    @Override\n    public void onGlobalLayout() {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onGlobalLayout();\n        } else {\n            super.onGlobalLayout();\n        }\n    }\n\n    @Override\n    public void superSurfaceCreated(SurfaceHolder holder) {\n        super.surfaceCreated(holder);\n    }\n\n    @Override\n    public void superSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n        super.surfaceChanged(holder, format, width, height);\n    }\n\n    @Override\n    public void superSurfaceRedrawNeeded(SurfaceHolder holder) {\n        super.surfaceRedrawNeeded(holder);\n    }\n\n    @Override\n    public void superSurfaceDestroyed(SurfaceHolder holder) {\n        super.surfaceDestroyed(holder);\n    }\n\n    @Override\n    public void superOnInputQueueCreated(InputQueue queue) {\n        super.onInputQueueCreated(queue);\n    }\n\n    @Override\n    public void superOnInputQueueDestroyed(InputQueue queue) {\n        super.onInputQueueDestroyed(queue);\n    }\n\n    @Override\n    public void superOnGlobalLayout() {\n        super.onGlobalLayout();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/activity-container/src/main/java/com/tencent/shadow/core/runtime/container/PluginContainerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport static com.tencent.shadow.core.runtime.container.DelegateProvider.LOADER_VERSION_KEY;\nimport static com.tencent.shadow.core.runtime.container.DelegateProvider.PROCESS_ID_KEY;\n\nimport android.app.Activity;\nimport android.content.res.Resources;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.Window;\n\nimport com.tencent.shadow.coding.java_build_config.BuildConfig;\n\n/**\n * 插件的容器Activity。PluginLoader将把插件的Activity放在其中。\n * PluginContainerActivity以委托模式将Activity的所有回调方法委托给DelegateProviderHolder提供的Delegate。\n *\n * @author cubershi\n */\npublic class PluginContainerActivity extends GeneratedPluginContainerActivity implements HostActivity, HostActivityDelegator {\n    private static final String TAG = \"PluginContainerActivity\";\n\n    HostActivityDelegate hostActivityDelegate;\n\n    private boolean isBeforeOnCreate = true;\n\n    public PluginContainerActivity() {\n        HostActivityDelegate delegate;\n        DelegateProvider delegateProvider = DelegateProviderHolder.getDelegateProvider(getDelegateProviderKey());\n        if (delegateProvider != null) {\n            delegate = delegateProvider.getHostActivityDelegate(this.getClass());\n            delegate.setDelegator(this);\n        } else {\n            Log.e(TAG, \"PluginContainerActivity: DelegateProviderHolder没有初始化\");\n            delegate = null;\n        }\n        super.hostActivityDelegate = delegate;\n        hostActivityDelegate = delegate;\n    }\n\n    protected String getDelegateProviderKey() {\n        return DelegateProviderHolder.DEFAULT_KEY;\n    }\n\n    final public Object getPluginActivity() {\n        if (hostActivityDelegate != null) {\n            return hostActivityDelegate.getPluginActivity();\n        } else {\n            return null;\n        }\n    }\n\n    @Override\n    final protected void onCreate(Bundle savedInstanceState) {\n        isBeforeOnCreate = false;\n        mHostTheme = null;//释放资源\n\n        boolean illegalIntent = isIllegalIntent(savedInstanceState);\n        if (illegalIntent) {\n            super.hostActivityDelegate = null;\n            hostActivityDelegate = null;\n            Log.e(TAG, \"illegalIntent savedInstanceState==\" + savedInstanceState + \" getIntent().getExtras()==\" + getIntent().getExtras());\n        }\n\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onCreate(savedInstanceState);\n        } else {\n            //这里是进程被杀后重启后走到，当需要恢复fragment状态的时候，由于系统保留了TAG，会因为找不到fragment引起crash\n            super.onCreate(null);\n            Log.e(TAG, \"onCreate: hostActivityDelegate==null finish activity\");\n            finish();\n            System.exit(0);\n        }\n    }\n\n    /**\n     * IllegalIntent指的是这些情况下的启动：\n     * 1.插件版本变化之后，残留于系统中的PendingIntent或系统因回收内存杀死进程残留的任务栈而启动。\n     * 由于插件版本变化，PluginLoader逻辑可能不一致，Intent中的参数可能不能满足新代码的启动条件。\n     * 2.外部的非法启动，无法确定一个插件的Activity。\n     * <p>\n     * <p>\n     * 3.不支持进程重启后莫名其妙的原因loader也加载了，但是可能要启动的plugin没有load，出现异常\n     *\n     * @param savedInstanceState onCreate时系统还回来的savedInstanceState\n     * @return <code>true</code>表示这次启动不是我们预料的，需要尽早finish并退出进程。\n     */\n    private boolean isIllegalIntent(Bundle savedInstanceState) {\n        Bundle extras = getIntent().getExtras();\n        if (extras == null && savedInstanceState == null) {\n            return true;\n        }\n        Bundle bundle;\n        bundle = savedInstanceState == null ? extras : savedInstanceState;\n        try {\n            String loaderVersion = bundle.getString(LOADER_VERSION_KEY);\n            long processVersion = bundle.getLong(PROCESS_ID_KEY);\n            return !BuildConfig.VERSION_NAME.equals(loaderVersion) || processVersion != DelegateProviderHolder.sCustomPid;\n        } catch (Throwable ignored) {\n            //捕获可能的非法Intent中包含我们根本反序列化不了的数据\n            return true;\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        if (hostActivityDelegate != null) {\n            hostActivityDelegate.onSaveInstanceState(outState);\n        } else {\n            super.onSaveInstanceState(outState);\n        }\n        //避免插件setIntent清空掉LOADER_VERSION_KEY\n        outState.putString(LOADER_VERSION_KEY, BuildConfig.VERSION_NAME);\n        outState.putLong(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid);\n    }\n\n    @Override\n    public HostActivity getHostActivity() {\n        return this;\n    }\n\n    @Override\n    public Activity getImplementActivity() {\n        return this;\n    }\n\n    @Override\n    public Window getImplementWindow() {\n        return getWindow();\n    }\n\n    /**\n     * Theme一旦设置了就不能更换Theme所在的Resouces了，见{@link Resources.Theme#setTo(Resources.Theme)}\n     * 而Activity在OnCreate之前需要设置Theme和使用Theme。我们需要在Activity OnCreate之后才能注入插件资源。\n     * 这就需要在Activity OnCreate之前不要调用Activity的setTheme方法，同时在getTheme时返回宿主的Theme资源。\n     * 注：{@link Activity#setTheme(int)}会触发初始化Theme，因此不能调用。\n     */\n    private Resources.Theme mHostTheme;\n\n    @Override\n    public Resources.Theme getTheme() {\n        if (isBeforeOnCreate) {\n            if (mHostTheme == null) {\n                mHostTheme = super.getResources().newTheme();\n            }\n            return mHostTheme;\n        } else {\n            return super.getTheme();\n        }\n    }\n\n    @Override\n    public void setTheme(int resid) {\n        if (!isBeforeOnCreate) {\n            super.setTheme(resid);\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\n//buildscript不能从其他gradle文件中apply，所以这段buildscript脚本存在于多个子构建中。\n//请更新buildscript时同步更新。\nbuildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        classpath 'com.tencent.shadow.coding:common-jar-settings'\n    }\n}\napply from: '../../../buildScripts/gradle/common.gradle'\n\nallprojects {\n    group 'com.tencent.shadow.core'\n    buildscript {\n        repositories {\n            if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n                maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n            } else {\n                google()\n                mavenCentral()\n            }\n        }\n    }\n}\n\ntasks.create('test').dependsOn subprojects.collect { it.getTasksByName('test', false) }\n"
  },
  {
    "path": "projects/sdk/core/common/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/common/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/common/ILoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.common;\n\npublic interface ILoggerFactory {\n\n    Logger getLogger(String name);\n}\n\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/common/InstalledApk.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.common;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * 安装完成的apk\n */\npublic class InstalledApk implements Parcelable {\n\n    public final String apkFilePath;\n\n    public final String oDexPath;\n\n    public final String libraryPath;\n\n    public final byte[] parcelExtras;\n\n    public InstalledApk(String apkFilePath, String oDexPath, String libraryPath) {\n        this(apkFilePath, oDexPath, libraryPath, null);\n    }\n\n    public InstalledApk(String apkFilePath, String oDexPath, String libraryPath, byte[] parcelExtras) {\n        this.apkFilePath = apkFilePath;\n        this.oDexPath = oDexPath;\n        this.libraryPath = libraryPath;\n        this.parcelExtras = parcelExtras;\n    }\n\n    protected InstalledApk(Parcel in) {\n        apkFilePath = in.readString();\n        oDexPath = in.readString();\n        libraryPath = in.readString();\n        int parcelExtrasLength = in.readInt();\n        if (parcelExtrasLength > 0) {\n            parcelExtras = new byte[parcelExtrasLength];\n        } else {\n            parcelExtras = null;\n        }\n        if (parcelExtras != null) {\n            in.readByteArray(parcelExtras);\n        }\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(apkFilePath);\n        dest.writeString(oDexPath);\n        dest.writeString(libraryPath);\n        dest.writeInt(parcelExtras == null ? 0 : parcelExtras.length);\n        if (parcelExtras != null) {\n            dest.writeByteArray(parcelExtras);\n        }\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<InstalledApk> CREATOR = new Creator<InstalledApk>() {\n        @Override\n        public InstalledApk createFromParcel(Parcel in) {\n            return new InstalledApk(in);\n        }\n\n        @Override\n        public InstalledApk[] newArray(int size) {\n            return new InstalledApk[size];\n        }\n    };\n}\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/common/Logger.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.common;\n\npublic interface Logger {\n\n    String getName();\n\n    boolean isTraceEnabled();\n\n    void trace(String msg);\n\n    void trace(String format, Object arg);\n\n    void trace(String format, Object arg1, Object arg2);\n\n    void trace(String format, Object... arguments);\n\n    void trace(String msg, Throwable t);\n\n    boolean isDebugEnabled();\n\n    void debug(String msg);\n\n    void debug(String format, Object arg);\n\n    void debug(String format, Object arg1, Object arg2);\n\n    void debug(String format, Object... arguments);\n\n    void debug(String msg, Throwable t);\n\n    boolean isInfoEnabled();\n\n    void info(String msg);\n\n    void info(String format, Object arg);\n\n    void info(String format, Object arg1, Object arg2);\n\n    void info(String format, Object... arguments);\n\n    void info(String msg, Throwable t);\n\n    boolean isWarnEnabled();\n\n    void warn(String msg);\n\n    void warn(String format, Object arg);\n\n    void warn(String format, Object... arguments);\n\n    void warn(String format, Object arg1, Object arg2);\n\n    void warn(String msg, Throwable t);\n\n    boolean isErrorEnabled();\n\n    void error(String msg);\n\n    void error(String format, Object arg);\n\n    void error(String format, Object arg1, Object arg2);\n\n    void error(String format, Object... arguments);\n\n    void error(String msg, Throwable t);\n}\n\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/common/LoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.common;\n\npublic final class LoggerFactory {\n\n    volatile private static ILoggerFactory sILoggerFactory;\n\n    public static void setILoggerFactory(ILoggerFactory loggerFactory) {\n        if (sILoggerFactory != null) {\n            throw new RuntimeException(\"不能重复初始化\");\n        }\n        sILoggerFactory = loggerFactory;\n    }\n\n    final public static Logger getLogger(Class<?> clazz) {\n        ILoggerFactory iLoggerFactory = getILoggerFactory();\n        return iLoggerFactory.getLogger(clazz.getName());\n    }\n\n    public static ILoggerFactory getILoggerFactory() {\n        if (sILoggerFactory == null) {\n            throw new RuntimeException(\"没有找到 ILoggerFactory 实现，请先调用setILoggerFactory\");\n        }\n        return sILoggerFactory;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/runtime/container/ContentProviderDelegateProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n\n/**\n * ContentProvider宿主容器委托提供者\n * <p>\n * 负责提供宿主容器委托实现\n *\n * @author owenguo\n */\npublic interface ContentProviderDelegateProvider {\n\n    /**\n     * 获取与delegator相应的HostContentProviderDelegator\n     *\n     * @return HostContentProvider被委托者\n     */\n    HostContentProviderDelegate getHostContentProviderDelegate();\n}\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/runtime/container/ContentProviderDelegateProviderHolder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\n\n/**\n * ContentProviderDelegateProvider依赖注入类\n * <p>\n * dynamic-pluginloader通过这个类实现将PluginLoader中的ContentProviderDelegateProvider实现注入到plugincontainer中。\n *\n * @author owenguo\n */\npublic class ContentProviderDelegateProviderHolder {\n    static ContentProviderDelegateProvider contentProviderDelegateProvider;\n\n\n    public static void setContentProviderDelegateProvider(ContentProviderDelegateProvider contentProviderDelegateProvider) {\n        ContentProviderDelegateProviderHolder.contentProviderDelegateProvider = contentProviderDelegateProvider;\n        notifyDelegateProviderHolderPrepare();\n    }\n\n    private static DelegateProviderHolderPrepareListener sPrepareListener;\n\n    public static void setDelegateProviderHolderPrepareListener(DelegateProviderHolderPrepareListener prepareListener) {\n        sPrepareListener = prepareListener;\n    }\n\n    private static void notifyDelegateProviderHolderPrepare() {\n        if (sPrepareListener != null) {\n            sPrepareListener.onPrepare();\n        }\n    }\n\n    interface DelegateProviderHolderPrepareListener {\n        void onPrepare();\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/runtime/container/HostContentProviderDelegate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport android.content.ContentValues;\nimport android.content.res.Configuration;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.CancellationSignal;\nimport android.os.ParcelFileDescriptor;\n\n/**\n * PluginContainerContentProvider的被委托者接口\n * <p>\n * 被委托者通过实现这个接口中声明的方法达到替代委托者实现的目的，从而将PluginContainerContentProvider的行为动态化。\n *\n * @author owenguo\n */\npublic interface HostContentProviderDelegate {\n\n    boolean onCreate();\n\n    void onConfigurationChanged(Configuration newConfig);\n\n    void onLowMemory();\n\n    void onTrimMemory(int level);\n\n    Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);\n\n    String getType(Uri uri);\n\n    Uri insert(Uri uri, ContentValues values);\n\n    int delete(Uri uri, String selection, String[] selectionArgs);\n\n    int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);\n\n    int bulkInsert(Uri uri, ContentValues[] values);\n\n    Bundle call(String method, String arg, Bundle extras);\n\n    ParcelFileDescriptor openFile(Uri uri, String mode);\n\n    ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal);\n}\n"
  },
  {
    "path": "projects/sdk/core/common/src/main/java/com/tencent/shadow/core/runtime/container/PluginContainerContentProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime.container;\n\nimport android.content.ContentProvider;\nimport android.content.ContentValues;\nimport android.content.res.Configuration;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.CancellationSignal;\nimport android.os.ParcelFileDescriptor;\nimport android.util.Log;\n\nimport java.io.FileNotFoundException;\n\npublic class PluginContainerContentProvider extends ContentProvider {\n\n    private final static String TAG = \"ContentProvider_\";\n\n    private HostContentProviderDelegate hostContentProviderDelegate;\n\n\n    public PluginContainerContentProvider() {\n        ContentProviderDelegateProvider p = ContentProviderDelegateProviderHolder.contentProviderDelegateProvider;\n        if (p != null) {\n            hostContentProviderDelegate = p.getHostContentProviderDelegate();\n        }\n        ContentProviderDelegateProviderHolder.setDelegateProviderHolderPrepareListener(new ContentProviderDelegateProviderHolder.DelegateProviderHolderPrepareListener() {\n            @Override\n            public void onPrepare() {\n                HostContentProviderDelegate delegate;\n                if (ContentProviderDelegateProviderHolder.contentProviderDelegateProvider != null) {\n                    delegate = ContentProviderDelegateProviderHolder.contentProviderDelegateProvider.getHostContentProviderDelegate();\n                    delegate.onCreate();\n                } else {\n                    Log.e(TAG, \"PluginContainerContentProvider: DelegateProviderHolder没有初始化\");\n                    delegate = null;\n                }\n                hostContentProviderDelegate = delegate;\n            }\n        });\n    }\n\n    @Override\n    public boolean onCreate() {\n        return false;\n    }\n\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.query(uri, projection, selection, selectionArgs, sortOrder);\n        }\n        return null;\n    }\n\n    @Override\n    public String getType(Uri uri) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.getType(uri);\n        }\n        return null;\n    }\n\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.insert(uri, values);\n        }\n        return null;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.delete(uri, selection, selectionArgs);\n        }\n        return 0;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.update(uri, values, selection, selectionArgs);\n        }\n        return 0;\n    }\n\n\n    @Override\n    public int bulkInsert(Uri uri, ContentValues[] values) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.bulkInsert(uri, values);\n        }\n        return 0;\n    }\n\n    @Override\n    public Bundle call(String method, String arg, Bundle extras) {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.call(method, arg, extras);\n        }\n        return null;\n    }\n\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        if (hostContentProviderDelegate != null) {\n            hostContentProviderDelegate.onConfigurationChanged(newConfig);\n        }\n    }\n\n    @Override\n    public void onLowMemory() {\n        if (hostContentProviderDelegate != null) {\n            hostContentProviderDelegate.onLowMemory();\n        }\n    }\n\n    @Override\n    public void onTrimMemory(int level) {\n        if (hostContentProviderDelegate != null) {\n            hostContentProviderDelegate.onTrimMemory(level);\n        }\n    }\n\n    @Override\n    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.openFile(uri, mode);\n        } else {\n            return super.openFile(uri, mode);\n        }\n    }\n\n    @Override\n    public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException {\n        checkHostContentProviderDelegate();\n        if (hostContentProviderDelegate != null) {\n            return hostContentProviderDelegate.openFile(uri, mode, signal);\n        } else {\n            return super.openFile(uri, mode);\n        }\n    }\n\n    private void checkHostContentProviderDelegate() {\n        if (hostContentProviderDelegate == null) {\n            throw new IllegalArgumentException(\"hostContentProviderDelegate is null ,请检查ContentProviderDelegateProviderHolder.setDelegateProviderHolderPrepareListener是否调用，或\" + this.getClass().getSimpleName() + \" 是否和插件在同一进程\");\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/build.gradle",
    "content": "apply plugin: 'java-gradle-plugin'\n\napply plugin: 'kotlin'\n\ngradlePlugin {\n    plugins {\n        shadow {\n            id = \"com.tencent.shadow.plugin\"\n            implementationClass = \"com.tencent.shadow.core.gradle.ShadowPlugin\"\n        }\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"com.android.tools.build:gradle:$build_gradle_version\"\n    implementation \"com.googlecode.json-simple:json-simple:$json_simple_version\"\n    implementation project(':transform')\n    implementation project(':manifest-parser')\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation gradleTestKit()\n\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompat.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.BaseExtension\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.api.BaseVariantOutput\nimport com.android.build.gradle.internal.dsl.ProductFlavor\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport java.io.File\n\n/**\n * 不同版本AGP的兼容层\n */\ninternal interface AGPCompat {\n    fun addFlavorDimension(baseExtension: BaseExtension, dimensionName: String)\n    fun setProductFlavorDefault(productFlavor: ProductFlavor, isDefault: Boolean)\n    fun getProcessResourcesTask(output: BaseVariantOutput): Task\n    fun getProcessResourcesFile(processResourcesTask: Task, variantName: String): File\n    fun getAaptAdditionalParameters(processResourcesTask: Task): List<String>\n    fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int\n    fun hasDeprecatedTransformApi(): Boolean\n    fun isGeneratePluginManifestByMergedManifest(\n        project: Project,\n        appExtension: AppExtension,\n        pluginVariant: ApplicationVariant\n    ): Boolean\n\n    fun getProcessManifestTask(output: BaseVariantOutput): Task\n    fun getProcessManifestFile(\n        project: Project,\n        pluginVariant: ApplicationVariant,\n        output: BaseVariantOutput\n    ): File\n\n    fun getRTxtFile(project: Project, processResourcesTask: Task?, variantName: String): File\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/AGPCompatImpl.kt",
    "content": "package com.tencent.shadow.core.gradle\n\nimport com.android.SdkConstants\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.BaseExtension\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.build.gradle.api.BaseVariantOutput\nimport com.android.build.gradle.internal.dsl.ProductFlavor\nimport com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask\nimport com.android.build.gradle.internal.scope.InternalArtifactType\nimport com.android.sdklib.AndroidVersion.VersionCodes\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.file.Directory\nimport org.gradle.api.file.DirectoryProperty\nimport org.gradle.api.provider.Property\nimport java.io.File\nimport kotlin.reflect.full.declaredMemberProperties\nimport kotlin.reflect.jvm.isAccessible\n\ninternal class AGPCompatImpl : AGPCompat {\n\n    override fun getProcessResourcesTask(output: BaseVariantOutput): Task =\n        try {\n            output.processResourcesProvider.get()\n        } catch (e: NoSuchMethodError) {\n            output.processResources\n        }\n\n    override fun getProcessResourcesFile(processResourcesTask: Task, variantName: String): File {\n        val capitalizeVariantName = variantName.capitalize()\n\n        return try {\n            File(\n                processResourcesTask.outputs.files.files.first { it.name.equals(\"out\") },\n                \"resources-$variantName.ap_\"\n            )\n\n            // 使用 resPackageOutputFolder\n            // 获取的路径和上方路径一致\n            // 备选（不推荐）\n            /*File(\n                (processResourcesTask as LinkApplicationAndroidResourcesTask).resPackageOutputFolder.asFile.get(),\n                \"resources-$variantName.ap_\"\n            )*/\n        } catch (ignored: Exception) {\n            // 高版本 AGP\n            try {\n                // 通过反射获取 KProperty： linkedResourcesOutputDir、linkedResourcesArtifactType\n                val linkedResourcesOutputDir =\n                    LinkApplicationAndroidResourcesTask::class.declaredMemberProperties.first {\n                        it.name == \"linkedResourcesOutputDir\"\n                    }.let {\n                        it.isAccessible = true\n                        it.getter.call(processResourcesTask) as DirectoryProperty\n                    }\n\n                @Suppress(\"UNCHECKED_CAST\")\n                val linkedResourcesArtifactType =\n                    LinkApplicationAndroidResourcesTask::class.declaredMemberProperties.first {\n                        it.name == \"linkedResourcesArtifactType\"\n                    }.let {\n                        it.isAccessible = true\n                        it.getter.call(processResourcesTask) as Property<InternalArtifactType<Directory>>\n                    }\n\n                File(\n                    linkedResourcesOutputDir.asFile.get(),\n                    linkedResourcesArtifactType.get().name().lowercase()\n                        .replace(\"_\", \"-\") + \"-\" + variantName + SdkConstants.DOT_RES\n                )\n            } catch (ignored: Exception) {\n                // 反射获取出错，备用\n                File(\n                    processResourcesTask.outputs.files.files.first { it.name.equals(\"process${capitalizeVariantName}Resources\") },\n                    \"linked-resources-binary-format-$variantName.ap_\"\n                )\n            }\n        }\n    }\n\n    @Suppress(\"PrivateApi\")\n    override fun getAaptAdditionalParameters(processResourcesTask: Task): List<String> =\n        try {\n            if (processResourcesTask is LinkApplicationAndroidResourcesTask) {\n                processResourcesTask.aaptAdditionalParameters.get()\n            } else {\n                TODO(\"不支持的AGP版本\")\n            }\n        } catch (ignored: NoSuchMethodError) {\n            //AGP 4.0.0\n            val aaptOptionsField =\n                LinkApplicationAndroidResourcesTask::class.java.getDeclaredField(\"aaptOptions\")\n            aaptOptionsField.isAccessible = true\n            val aaptOptions = aaptOptionsField.get(processResourcesTask)\n            val additionalParametersField = try {\n                aaptOptions.javaClass.getDeclaredField(\"additionalParameters\")\n            } catch (ignored: NoSuchFieldException) {\n                //AGP 3.4.0\n                aaptOptions.javaClass.superclass.getDeclaredField(\"additionalParameters\")\n            }\n\n            additionalParametersField.isAccessible = true\n            @Suppress(\"UNCHECKED_CAST\")\n            val additionalParameters = additionalParametersField.get(aaptOptions) as? List<String>\n            additionalParameters ?: listOf()\n        }\n\n    override fun addFlavorDimension(baseExtension: BaseExtension, dimensionName: String) {\n        val flavorDimensionList = baseExtension.flavorDimensionList\n                as MutableList<String>? // AGP 3.6.0版本可能返回null\n        if (flavorDimensionList != null) {\n            flavorDimensionList.add(dimensionName)\n        } else {\n            baseExtension.flavorDimensions(dimensionName)\n        }\n    }\n\n    override fun setProductFlavorDefault(productFlavor: ProductFlavor, isDefault: Boolean) {\n        try {\n            productFlavor.isDefault = isDefault\n        } catch (ignored: NoSuchMethodError) {\n            // AGP 3.6.0版本没有这个方法，就不设置了。\n            // 设置Default主要是为了IDE中的Build Variants上下文自动选择时不要选成插件，\n            // 以便在IDE直接运行插件apk模块时运行Normal版本\n        }\n    }\n\n    override fun getMinSdkVersion(pluginVariant: ApplicationVariant): Int {\n        // AGP在版本升级中修改了MergedFlavor的包名，但是它实现的ProductFlavor接口没有变\n        val mergedFlavor = pluginVariant.mergedFlavor as com.android.builder.model.ProductFlavor\n        return mergedFlavor.minSdkVersion?.apiLevel ?: VersionCodes.BASE\n    }\n\n    override fun hasDeprecatedTransformApi(): Boolean {\n        try {\n            val version = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION\n            val majorVersion = version.substringBefore('.', \"0\").toInt()\n            if (majorVersion >= 8) {\n                return false//能parse出来主版本号大于等于8，我们就认为旧版Transform API不可用了。\n            }\n        } catch (ignored: Error) {\n        }\n\n        //读取版本号失败，就推测是旧版本的AGP，就应该有旧版本的Transform API\n        return true\n    }\n\n    override fun isGeneratePluginManifestByMergedManifest(\n        project: Project,\n        appExtension: AppExtension,\n        pluginVariant: ApplicationVariant\n    ): Boolean {\n        // 可以通过配置强制开启\n        if (\"true\" == project.findProperty(\"shadow.generatePluginManifestUseMergedManifest\")) {\n            return true\n        }\n        // 没有开启无用资源删减，则不使用 merged manifest\n        try {\n            if (!pluginVariant.buildType.isMinifyEnabled) {\n                return false\n            }\n            // AppExtension 获取的 BuildType 无法获取 isShrinkResources 属性，只能查找原始的 BuildType 实现。\n            if (!appExtension.buildTypes.getByName(pluginVariant.buildType.name).isShrinkResources) {\n                return false\n            }\n        } catch (ignored: Error) {\n        }\n\n        // 开启无用资源删减功能，同时AGP 版本至少要为 8.9.0\n        try {\n            val version = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION\n            val majorVersion = version.substringBefore('.', \"0\").toInt()\n            if (majorVersion > 8) {\n                return true\n            }\n            if (majorVersion == 8) {\n                val minorVersion =\n                    version.substringAfter('.').substringBefore('.').toIntOrNull() ?: 0\n                return minorVersion >= 9\n            }\n        } catch (ignored: Error) {\n        }\n        // 默认不使用 merged manifest 。\n        return false\n    }\n\n    /**\n     * 获取生成最终 AndroidManifest.xml 文件的任务。\n     */\n    override fun getProcessManifestTask(output: BaseVariantOutput): Task {\n        return try {\n            output.processManifestProvider.get()\n        } catch (_: Error) {\n            output.processManifest\n        }\n    }\n\n    /**\n     * 获取合并后的 AndroidManifest.xml 文件。\n     *\n     * 优先从 processManifest 任务输出获取，否则搜索 intermediates 目录。\n     */\n    override fun getProcessManifestFile(\n        project: Project,\n        pluginVariant: ApplicationVariant,\n        output: BaseVariantOutput\n    ): File {\n        // 1. 优先从任务输出获取\n        try {\n            output.processManifestProvider.get().outputs.files.files.forEach {\n                findFileByName(it, \"AndroidManifest.xml\")?.let { file -> return file }\n            }\n        } catch (_: Exception) {\n            // 忽略\n        }\n\n        val variantName = pluginVariant.name\n\n        // 2. 搜索中间产物目录\n        return listOf(\n            \"intermediates/merged_manifests/$variantName\", // AGP 4.x/7.x/8.x\n            \"intermediates/manifests/full/$variantName\", // AGP 3.x\n        )\n            .map { File(project.buildDir, it) }\n            .first {\n                findFileByName(it, \"AndroidManifest.xml\") != null\n            }\n    }\n\n    /**\n     * 获取 R.txt 文件。\n     *\n     * 优先从 processResources 任务的输出获取（最准确）， 否则搜索 intermediates 目录。\n     */\n    override fun getRTxtFile(\n        project: Project,\n        processResourcesTask: Task?,\n        variantName: String\n    ): File {\n        // 1. 优先尝试从任务输出中查找\n        if (processResourcesTask != null) {\n            try {\n                processResourcesTask.outputs.files.files.forEach {\n                    findFileByName(it, \"R.txt\")?.let { file -> return file }\n                }\n            } catch (_: Exception) {\n                // 忽略解析错误，继续走备选路径\n            }\n        }\n\n        // 2. 根据 AGP 版本已知的中间产物路径搜索\n        return listOf(\n            \"intermediates/runtime_symbol_list/$variantName\", // AGP 4.x/7.x/8.x\n            \"intermediates/symbols/$variantName\",\n            \"intermediates/bundles/$variantName\"\n        )\n            .map { File(project.buildDir, it) }\n            .first {\n                findFileByName(it, \"R.txt\") != null\n            }\n    }\n\n    /**\n     * 搜索指定目录下指定文件名的文件。\n     *\n     * @return 文件对象，若找不到则返回 null 。\n     */\n    private fun findFileByName(file: File, fileName: String): File? {\n        if (!file.exists()) {\n            return null\n        }\n        if (file.isFile && file.name == fileName) {\n            return file\n        }\n        if (file.isDirectory) {\n            val subFiles = file.listFiles()\n            if (subFiles != null) {\n                for (subFile in subFiles) {\n                    val resultFile = findFileByName(subFile, fileName)\n                    if (resultFile != null) {\n                        return resultFile\n                    }\n                }\n            }\n        }\n        return null\n    }\n\n    companion object {\n        fun getStringFromProperty(x: Any?): String {\n            return when (x) {\n                is String -> x\n                is Property<*> -> x.get() as String\n                else -> throw Error(\"不支持的AGP版本\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/CreatePackagePluginTask.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport com.tencent.shadow.core.gradle.extensions.PackagePluginExtension\nimport com.tencent.shadow.core.gradle.extensions.PluginBuildType\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.tasks.bundling.Zip\nimport java.io.BufferedWriter\nimport java.io.File\nimport java.io.FileWriter\n\ninternal fun createPackagePluginTask(project: Project, buildType: PluginBuildType): Task {\n    return project.tasks.create(\"package${buildType.name.capitalize()}Plugin\", Zip::class.java) {\n        project.logger.info(\"PackagePluginTask task run\")\n\n        //runtime apk file\n        val runtimeApkName: String = buildType.runtimeApkConfig.first\n        var runtimeFile: File? = null\n        if (runtimeApkName.isNotEmpty()) {\n            runtimeFile = ShadowPluginHelper.getRuntimeApkFile(project, buildType, false)\n        }\n\n\n        //loader apk file\n        val loaderApkName: String = buildType.loaderApkConfig.first\n        var loaderFile: File? = null\n        if (loaderApkName.isNotEmpty()) {\n            loaderFile = ShadowPluginHelper.getLoaderApkFile(project, buildType, false)\n        }\n\n\n        //config file\n        val targetConfigFile =\n            File(project.buildDir.absolutePath + \"/intermediates/generatePluginConfig/${buildType.name}/config.json\")\n        targetConfigFile.parentFile.mkdirs()\n\n\n        //all plugin apks\n        val pluginFiles: MutableList<File> = mutableListOf()\n        for (i in buildType.pluginApks) {\n            pluginFiles.add(ShadowPluginHelper.getPluginFile(project, i, false))\n        }\n\n\n        it.group = \"plugin\"\n        it.description = \"打包插件\"\n        it.outputs.upToDateWhen { false }\n        if (runtimeFile != null) {\n            pluginFiles.add(runtimeFile)\n        }\n        if (loaderFile != null) {\n            pluginFiles.add(loaderFile)\n        }\n        it.from(pluginFiles, targetConfigFile)\n\n        val packagePlugin = project.extensions.findByName(\"packagePlugin\")\n        val extension = packagePlugin as PackagePluginExtension\n\n        val suffix = if (extension.archiveSuffix.isEmpty()) \"\" else extension.archiveSuffix\n        val prefix = if (extension.archivePrefix.isEmpty()) \"plugin\" else extension.archivePrefix\n\n        val name =\n            if (suffix.isEmpty()) {\n                \"$prefix-${buildType.name}.zip\"\n            } else {\n                \"$prefix-${buildType.name}-$suffix.zip\"\n            }\n        it.archiveFileName.set(name)\n\n        it.destinationDirectory.set(\n            File(if (extension.destinationDir.isEmpty()) \"${project.rootDir}/build\" else extension.destinationDir)\n        )\n    }.dependsOn(createGenerateConfigTask(project, buildType))\n}\n\nprivate fun createGenerateConfigTask(project: Project, buildType: PluginBuildType): Task {\n    project.logger.info(\"GenerateConfigTask task run\")\n    val packagePlugin = project.extensions.findByName(\"packagePlugin\")\n    val extension = packagePlugin as PackagePluginExtension\n\n    //runtime apk build task\n    val runtimeApkName = buildType.runtimeApkConfig.first\n    var runtimeTask = \"\"\n    if (runtimeApkName.isNotEmpty()) {\n        runtimeTask = buildType.runtimeApkConfig.second\n        project.logger.info(\"runtime task = $runtimeTask\")\n    }\n\n\n    //loader apk build task\n    val loaderApkName = buildType.loaderApkConfig.first\n    var loaderTask = \"\"\n    if (loaderApkName.isNotEmpty()) {\n        loaderTask = buildType.loaderApkConfig.second\n        project.logger.info(\"loader task = $loaderTask\")\n    }\n\n\n    val targetConfigFile =\n        File(project.buildDir.absolutePath + \"/intermediates/generatePluginConfig/${buildType.name}/config.json\")\n\n\n    val pluginApkTasks: MutableList<String> = mutableListOf()\n    for (i in buildType.pluginApks) {\n        val task = i.buildTask\n        project.logger.info(\"pluginApkProjects task = $task\")\n        pluginApkTasks.add(task)\n    }\n\n    val task = project.tasks.create(\"generate${buildType.name.capitalize()}Config\") {\n        it.group = \"plugin\"\n        it.description = \"生成插件配置文件\"\n        it.outputs.file(targetConfigFile)\n        it.outputs.upToDateWhen { false }\n    }\n        .dependsOn(pluginApkTasks)\n        .doLast {\n\n            project.logger.info(\"generateConfig task begin\")\n            val json = extension.toJson(project, loaderApkName, runtimeApkName, buildType)\n\n            val bizWriter = BufferedWriter(FileWriter(targetConfigFile))\n            bizWriter.write(json.toJSONString())\n            bizWriter.newLine()\n            bizWriter.flush()\n            bizWriter.close()\n\n            project.logger.info(\"generateConfig task done\")\n        }\n    if (loaderTask.isNotEmpty()) {\n        task.dependsOn(loaderTask)\n    }\n    if (runtimeTask.isNotEmpty()) {\n        task.dependsOn(runtimeTask)\n    }\n    return task\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPlugin.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport com.android.build.api.artifact.ScopedArtifact\nimport com.android.build.api.variant.ApplicationAndroidComponentsExtension\nimport com.android.build.api.variant.ScopedArtifacts\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.BaseExtension\nimport com.android.build.gradle.api.ApplicationVariant\nimport com.android.sdklib.AndroidVersion.VersionCodes\nimport com.tencent.shadow.core.gradle.extensions.PackagePluginExtension\nimport com.tencent.shadow.core.manifest_parser.createManifestValueParser\nimport com.tencent.shadow.core.manifest_parser.generatePluginManifest\nimport com.tencent.shadow.core.transform.DeprecatedTransformWrapper\nimport com.tencent.shadow.core.transform.GradleTransformWrapper\nimport com.tencent.shadow.core.transform.ShadowTransform\nimport com.tencent.shadow.core.transform_kit.AndroidClassPoolBuilder\nimport com.tencent.shadow.core.transform_kit.ClassPoolBuilder\nimport org.gradle.api.*\nimport org.gradle.api.tasks.compile.JavaCompile\nimport java.io.File\nimport java.net.URLClassLoader\nimport java.util.zip.ZipFile\n\nclass ShadowPlugin : Plugin<Project> {\n\n    private lateinit var androidClassPoolBuilder: ClassPoolBuilder\n    private lateinit var contextClassLoader: ClassLoader\n    private lateinit var agpCompat: AGPCompat\n\n    override fun apply(project: Project) {\n        agpCompat = buildAgpCompat(project)\n        val baseExtension = project.extensions.getByName(\"android\") as BaseExtension\n\n        //在这里取到的contextClassLoader包含运行时库(classpath方式引入的)shadow-runtime\n        contextClassLoader = Thread.currentThread().contextClassLoader\n        val lateInitBuilder = object : ClassPoolBuilder {\n            override fun build() = androidClassPoolBuilder.build()\n        }\n\n        val shadowExtension = project.extensions.create(\"shadow\", ShadowExtension::class.java)\n        if (!project.hasProperty(\"disable_shadow_transform\")) {\n            val shadowTransform = ShadowTransform(\n                project,\n                lateInitBuilder,\n                { shadowExtension.transformConfig.useHostContext }\n            )\n            if (agpCompat.hasDeprecatedTransformApi()) {\n                baseExtension.registerTransform(\n                    DeprecatedTransformWrapper(\n                        project,\n                        shadowTransform\n                    )\n                )\n            } else {\n                val androidComponentsExtension =\n                    project.extensions.getByName(\"androidComponents\") as ApplicationAndroidComponentsExtension\n                androidComponentsExtension.onVariants(\n                    selector = androidComponentsExtension.selector()\n                        .withFlavor(\n                            ShadowTransform.DimensionName\n                                    to ShadowTransform.ApplyShadowTransformFlavorName\n                        )\n                ) { variant ->\n                    val taskProvider = project.tasks.register(\n                        \"${variant.name}ShadowTransform\",\n                        GradleTransformWrapper::class.java,\n                        shadowTransform\n                    )\n                    variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)\n                        .use<GradleTransformWrapper>(taskProvider)\n                        .toTransform(\n                            ScopedArtifact.CLASSES,\n                            GradleTransformWrapper::allJars,\n                            GradleTransformWrapper::allDirectories,\n                            GradleTransformWrapper::output\n                        )\n                }\n            }\n        }\n\n        addFlavorForTransform(baseExtension)\n\n        project.extensions.create(\"packagePlugin\", PackagePluginExtension::class.java, project)\n\n        project.afterEvaluate {\n            initAndroidClassPoolBuilder(baseExtension, project)\n\n            createPackagePluginTasks(project)\n\n            addLocateApkanalyzerTask(project)\n\n            onEachPluginVariant(project) { pluginVariant ->\n                checkAaptPackageIdConfig(pluginVariant)\n\n                val appExtension: AppExtension =\n                    project.extensions.getByType(AppExtension::class.java)\n                if (agpCompat.isGeneratePluginManifestByMergedManifest(\n                        project,\n                        appExtension,\n                        pluginVariant\n                    )\n                ) {\n                    createGeneratePluginManifestTasksByMergedManifest(\n                        project,\n                        appExtension,\n                        pluginVariant\n                    )\n                } else {\n                    createGeneratePluginManifestTasks(project, appExtension, pluginVariant)\n                }\n            }\n        }\n\n        checkKotlinAndroidPluginForPluginManifestTask(project)\n    }\n\n    private fun addLocateApkanalyzerTask(project: Project) {\n        val appExtension: AppExtension =\n            project.extensions.getByType(AppExtension::class.java)\n        val sdkDirectory = appExtension.sdkDirectory\n        val outputFile = project.locateApkanalyzerResultPath()\n\n        project.tasks.register(locateApkanalyzerTaskName) {\n            it.inputs.property(\"sdkPath\", sdkDirectory.path)\n            it.outputs.file(outputFile).withPropertyName(\"locateApkanalyzerResultPath\")\n\n            it.doLast {\n                // 如果其他project的此任务执行过了，就不用再查找了\n                if (outputFile.exists() && File(outputFile.readText()).exists()) {\n                    return@doLast\n                }\n\n                // 找出apkanalyzer.jar.它是build tool的一部分，但位置随着版本有变化，所以这里用搜索文件确定位置\n                // 如果有多个版本，随机取第一个，因为只用decodeXml方法，预期不同版本没什么区别。\n                val apkanalyzerJarFile =\n                    try {\n                        sdkDirectory.walk().filter { file ->\n                            listOf(\n                                \"apkanalyzer.jar\",// 低版本build tools\n                                \"apkanalyzer-classpath.jar\",// 2020-06-05 cmdline-tools version 2.0\n                            ).any { it == file.name }\n                        }.first()\n                    } catch (e: NoSuchElementException) {\n                        // https://developer.android.com/studio/command-line/apkanalyzer\n                        // https://developer.android.com/studio/releases/sdk-tools\n                        // https://cs.android.com/android/platform/superproject/+/master:prebuilts/cmdline-tools/tools/bin/apkanalyzer;l=67;bpv=1;bpt=0\n                        throw Error(\n                            \"找不到apkanalyzer.它来自：\" +\n                                    \"cmdline-tools.\" +\n                                    \"如果高版本SDK也找不到这个文件，Shadow就需要更新了。\"\n                        )\n                    }\n\n                outputFile.parentFile.mkdirs()\n                outputFile.writeText(apkanalyzerJarFile.absolutePath)\n            }\n        }\n    }\n\n    /**\n     * GeneratePluginManifestTask会向android DSL添加新的java源码目录，\n     * 而kotlin-android会在syncKotlinAndAndroidSourceSets中接管java的源码目录，\n     * 从而使后添加到android DSL中的java目录失效。\n     */\n    private fun checkKotlinAndroidPluginForPluginManifestTask(project: Project) {\n        if (project.plugins.hasPlugin(\"kotlin-android\")) {\n            throw Error(\"必须在kotlin-android之前应用com.tencent.shadow.plugin\")\n        }\n    }\n\n    private fun createPackagePluginTasks(project: Project) {\n        val packagePlugin = project.extensions.findByName(\"packagePlugin\")\n        val extension = packagePlugin as PackagePluginExtension\n        val buildTypes = extension.buildTypes\n\n        val tasks = mutableListOf<Task>()\n        for (i in buildTypes) {\n            project.logger.info(\"buildTypes = \" + i.name)\n            val task = createPackagePluginTask(project, i)\n            tasks.add(task)\n        }\n        if (tasks.isNotEmpty()) {\n            project.tasks.create(\"packageAllPlugin\") {\n                it.group = \"plugin\"\n                it.description = \"打包所有插件\"\n            }.dependsOn(tasks)\n        }\n    }\n\n    private fun onEachPluginVariant(project: Project, actions: (ApplicationVariant) -> Unit) {\n        val appExtension: AppExtension = project.extensions.getByType(AppExtension::class.java)\n        val pluginVariants = appExtension.applicationVariants.filter { variant ->\n            variant.productFlavors.any { flavor ->\n                flavor.dimension == ShadowTransform.DimensionName &&\n                        flavor.name == ShadowTransform.ApplyShadowTransformFlavorName\n            }\n        }\n\n        checkPluginVariants(pluginVariants, appExtension, project.name)\n\n        pluginVariants.forEach(actions)\n    }\n\n    /**\n     * 创建生成PluginManifest.java的任务\n     */\n    @Suppress(\"PrivateApi\")// for use BinaryXmlParser(apkanalyzer)\n    private fun createGeneratePluginManifestTasks(\n        project: Project,\n        appExtension: AppExtension,\n        pluginVariant: ApplicationVariant\n    ) {\n        val output = pluginVariant.outputs.first()\n\n        val variantName = pluginVariant.name\n        val capitalizeVariantName = variantName.capitalize()\n\n        // 找出ap_文件\n        val processResourcesTask = agpCompat.getProcessResourcesTask(output)\n        val processedResFile = agpCompat.getProcessResourcesFile(processResourcesTask, variantName)\n\n        // decodeBinaryManifestTask输出的apkanalyzer manifest print结果文件\n        val decodeXml = File(\n            project.buildDir,\n            \"intermediates/decodeBinaryManifest/$variantName/AndroidManifest.xml\"\n        )\n\n        // 添加decodeXml任务\n        val decodeBinaryManifestTask =\n            project.tasks.register(\"decode${capitalizeVariantName}BinaryManifest\") {\n                it.dependsOn(locateApkanalyzerTaskName)\n                it.dependsOn(processResourcesTask)\n                it.inputs.file(processedResFile)\n                it.outputs.file(decodeXml).withPropertyName(\"decodeXml\")\n\n                it.doLast {\n                    val zipFile = ZipFile(processedResFile)\n                    val binaryXml = zipFile.getInputStream(\n                        zipFile.getEntry(\"AndroidManifest.xml\")\n                    ).readBytes()\n\n                    val outputXmlBytes = decodeXml(project, binaryXml)\n                    decodeXml.parentFile.mkdirs()\n                    decodeXml.writeBytes(outputXmlBytes)\n                }\n            }\n\n\n        // 添加生成PluginManifest.java任务\n        val pluginManifestSourceDir =\n            File(project.buildDir, \"generated/source/pluginManifest/$variantName\")\n        val generatePluginManifestTask =\n            project.tasks.register(\"generate${capitalizeVariantName}PluginManifest\") {\n                it.dependsOn(decodeBinaryManifestTask)\n                it.inputs.file(decodeXml)\n                it.outputs.dir(pluginManifestSourceDir).withPropertyName(\"pluginManifestSourceDir\")\n\n                it.doLast {\n                    generatePluginManifest(\n                        decodeXml,\n                        pluginManifestSourceDir,\n                        \"com.tencent.shadow.core.manifest_parser\"\n                    )\n                }\n            }\n        val javacTask = project.tasks.getByName(\"compile${capitalizeVariantName}JavaWithJavac\")\n        javacTask.dependsOn(generatePluginManifestTask)\n\n        // 把PluginManifest.java添加为源码\n        val relativePath =\n            project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()\n        (javacTask as JavaCompile).source(project.fileTree(relativePath))\n    }\n\n    private fun createGeneratePluginManifestTasksByMergedManifest(\n        project: Project,\n        appExtension: AppExtension,\n        pluginVariant: ApplicationVariant\n    ) {\n        val output = pluginVariant.outputs.first()\n\n        val variantName = pluginVariant.name\n        val capitalizeVariantName = variantName.capitalize()\n\n        // 添加生成PluginManifest.java任务\n        val pluginManifestSourceDir =\n            File(project.buildDir, \"generated/source/pluginManifest/$variantName\")\n\n        val javacTask = project.tasks.getByName(\"compile${capitalizeVariantName}JavaWithJavac\")\n\n        val generatePluginManifestTask =\n            project.tasks.register(\"generate${capitalizeVariantName}PluginManifest\") {\n                // 依赖 processManifest 任务以获取最终的 AndroidManifest.xml\n                val processManifestTask = agpCompat.getProcessManifestTask(output)\n                // 依赖 processResources 任务以获取 R.txt\n                val processResourcesTask = agpCompat.getProcessResourcesTask(output)\n                it.dependsOn(processManifestTask)\n                it.dependsOn(processResourcesTask)\n\n                it.outputs.dir(pluginManifestSourceDir).withPropertyName(\"pluginManifestSourceDir\")\n\n                it.doLast {\n                    // 解析合并后的 AndroidManifest.xml + R.txt\n                    // 这种方案直接解析 XML 格式的 AndroidManifest.xml ，不再依赖 aapt2 产生的二进制产物\n                    val mergedManifest =\n                        agpCompat.getProcessManifestFile(project, pluginVariant, output)\n                    val rTxt = agpCompat.getRTxtFile(project, processResourcesTask, variantName)\n                    val manifestValueParser = createManifestValueParser(rTxt)\n                    generatePluginManifest(\n                        mergedManifest,\n                        pluginManifestSourceDir,\n                        \"com.tencent.shadow.core.manifest_parser\",\n                        manifestValueParser\n                    )\n                }\n            }\n        javacTask.dependsOn(generatePluginManifestTask)\n\n        // 把PluginManifest.java添加为源码\n        val relativePath =\n            project.projectDir.toPath().relativize(pluginManifestSourceDir.toPath()).toString()\n        (javacTask as JavaCompile).source(project.fileTree(relativePath))\n    }\n\n    /**\n     * 反射apkanalyzer中的BinaryXmlParser类的decodeXml方法\n     */\n    @Suppress(\"PrivateApi\")\n    private fun decodeXml(project: Project, binaryXml: ByteArray): ByteArray {\n        val jarPath = File(project.locateApkanalyzerResultPath().readText())\n        val tempCL = URLClassLoader(arrayOf(jarPath.toURL()), contextClassLoader)\n        val binaryXmlParserClass =\n            tempCL.loadClass(\"com.android.tools.apk.analyzer.BinaryXmlParser\")\n        return try {\n            decodeXmlMethodV1(binaryXmlParserClass, binaryXml)\n        } catch (ignored: Exception) {\n            decodeXmlMethodV2(binaryXmlParserClass, binaryXml)\n        }\n    }\n\n    private fun decodeXmlMethodV1(binaryXmlParserClass: Class<*>, binaryXml: ByteArray): ByteArray {\n        val decodeXmlMethod = binaryXmlParserClass.getDeclaredMethod(\n            \"decodeXml\",\n            String::class.java,\n            ByteArray::class.java\n        )\n        return decodeXmlMethod.invoke(\n            null,\n            \"AndroidManifest.xml\",\n            binaryXml\n        ) as ByteArray\n    }\n\n    /**\n     * 新版本代码中删掉了一个String参数，这个参数原来只用于log输出了\n     * https://cs.android.com/android-studio/platform/tools/base/+/6a81855c2fa102ae4532ad9a645e40177770a26a:apkparser/analyzer/src/main/java/com/android/tools/apk/analyzer/BinaryXmlParser.java;dlc=598c38100e4fb2b001385faea994fcb54cc515b1\n     */\n    private fun decodeXmlMethodV2(binaryXmlParserClass: Class<*>, binaryXml: ByteArray): ByteArray {\n        val decodeXmlMethod = binaryXmlParserClass.getDeclaredMethod(\n            \"decodeXml\",\n            ByteArray::class.java\n        )\n        return decodeXmlMethod.invoke(\n            null,\n            binaryXml\n        ) as ByteArray\n    }\n\n    /**\n     * 检查插件是否修改了资源ID分区\n     *\n     * 因为CreateResourceBloc在为插件创建Resources对象时，\n     * 将宿主和插件的apk都放进去了，所以不能让宿主和插件的资源ID冲突。详见CreateResourceBloc注释。\n     *\n     * 此任务只是检查任务，对构建无影响。\n     */\n    private fun checkAaptPackageIdConfig(pluginVariant: ApplicationVariant) {\n        val output = pluginVariant.outputs.first()\n        val minSdkVersion = agpCompat.getMinSdkVersion(pluginVariant)\n        val processResourcesTask = agpCompat.getProcessResourcesTask(output)\n\n        processResourcesTask.doFirst {\n            val parameterList = agpCompat.getAaptAdditionalParameters(processResourcesTask)\n            var foundPackageIdParameter = false\n            parameterList.forEachIndexed { index, parameter ->\n                if (parameter == \"--package-id\" && parameterList.size >= index + 2) {\n                    val packageIdSetting = parameterList[index + 1]\n                    val packageIdValue = Integer.decode(packageIdSetting)\n\n                    if (minSdkVersion > VersionCodes.O) {\n                        if (packageIdValue <= 0x7f) {\n                            throw Error(\"minSdkVersion大于26时--package-id必须大于0x7f\")\n                        } else {\n                            foundPackageIdParameter = true\n                        }\n                    } else {\n                        if (packageIdValue >= 0x7f) {\n                            /*\n                            为了兼容minSDK小于26，且packageId大于0x7f时Android系统的bug，aapt对id进行了修改，\n                            导致Resources中记录的id值和layout中使用的id值不一致。\n                            但是minSDK小于26时可以使用--allow-reserved-package-id选项使用小于0x7f的值。\n                            https://android.googlesource.com/platform/frameworks/base/+/master/tools/aapt2/readme.md#version-2_14\n                            https://developer.android.com/studio/command-line/aapt2#link_options\n                             */\n                            throw Error(\n                                \"minSdkVersion小于26时--package-id必须小于0x7f，\" +\n                                        \"同时使用--allow-reserved-package-id选项。\"\n                            )\n                        } else {\n                            foundPackageIdParameter = true\n                        }\n                    }\n                }\n            }\n            if (!foundPackageIdParameter) {\n                val example1 = \"aaptOptions {\\n\" +\n                        \"    additionalParameters \\\"--package-id\\\", \\\"0x80\\\"\\n\" +\n                        \"}\"\n                val example2 = \"aaptOptions {\\n\" +\n                        \"    additionalParameters \\\"--package-id\\\", \\\"0x7E\\\", \\\"--allow-reserved-package-id\\\"\\n\" +\n                        \"}\"\n                val example = if (minSdkVersion > VersionCodes.O) example1 else example2\n                throw Error(\n                    \"插件需要利用aapt2的修改资源ID前缀的选项使其与宿主不同。\\n\" +\n                            \"没有找到--package-id参数。示例：\\n\" + example\n                )\n            }\n        }\n    }\n\n    private fun checkPluginVariants(\n        pluginVariants: List<ApplicationVariant>,\n        appExtension: AppExtension,\n        projectName: String\n    ) {\n        if (pluginVariants.isEmpty()) {\n            val errorMessage = StringBuilder()\n            errorMessage.appendLine(\"在${projectName}中找不到Shadow所添加的Dimension flavor\")\n            errorMessage.appendLine(\"当前所有flavor打印如下：\")\n            appExtension.applicationVariants.forEach { variant ->\n                errorMessage.appendLine(\"variant.name：${variant.name}\")\n                variant.productFlavors.forEach { flavor ->\n                    errorMessage.appendLine(\n                        \"flavor.name：${flavor.name} flavor.dimension：${flavor.dimension} \"\n                    )\n                }\n            }\n            errorMessage.appendLine(\"提示：添加flavorDimension时，不要覆盖已有flavorDimension\")\n            errorMessage.appendLine(\"示例：flavorDimensions(*flavorDimensionList, 'new')\")\n            throw Error(errorMessage.toString())\n        }\n    }\n\n    private fun addFlavorForTransform(baseExtension: BaseExtension) {\n        agpCompat.addFlavorDimension(baseExtension, ShadowTransform.DimensionName)\n        try {\n            baseExtension.productFlavors.create(ShadowTransform.NoShadowTransformFlavorName) {\n                it.dimension = ShadowTransform.DimensionName\n                agpCompat.setProductFlavorDefault(it, true)\n            }\n            baseExtension.productFlavors.create(ShadowTransform.ApplyShadowTransformFlavorName) {\n                it.dimension = ShadowTransform.DimensionName\n                agpCompat.setProductFlavorDefault(it, false)\n            }\n        } catch (e: InvalidUserDataException) {\n            throw Error(\"请在android{} DSL之前apply plugin: 'com.tencent.shadow.plugin'\", e)\n        }\n    }\n\n    private fun initAndroidClassPoolBuilder(\n        baseExtension: BaseExtension,\n        project: Project\n    ) {\n        val sdkDirectory = baseExtension.sdkDirectory\n        val compileSdkVersion =\n            baseExtension.compileSdkVersion ?: throw IllegalStateException(\"compileSdkVersion获取失败\")\n        val androidJarPath = \"platforms/${compileSdkVersion}/android.jar\"\n        val androidJar = File(sdkDirectory, androidJarPath)\n\n        androidClassPoolBuilder = AndroidClassPoolBuilder(project, contextClassLoader, androidJar)\n    }\n\n    open class ShadowExtension {\n        var transformConfig = TransformConfig()\n        fun transform(action: Action<in TransformConfig>) {\n            action.execute(transformConfig)\n        }\n    }\n\n    class TransformConfig {\n        var useHostContext: Array<String> = emptyArray()\n    }\n\n    companion object {\n        const val locateApkanalyzerTaskName = \"locateApkanalyzer\"\n        private fun Project.locateApkanalyzerResultPath() =\n            File(rootProject.buildDir, \"shadow/ApkanalyzerPath.txt\")\n\n        private fun buildAgpCompat(project: Project): AGPCompat {\n            return AGPCompatImpl()\n        }\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/ShadowPluginHelper.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport com.tencent.shadow.core.gradle.extensions.PackagePluginExtension\nimport com.tencent.shadow.core.gradle.extensions.PluginApkConfig\nimport com.tencent.shadow.core.gradle.extensions.PluginBuildType\nimport org.gradle.api.Project\nimport java.io.File\nimport java.io.FileInputStream\nimport java.security.MessageDigest\nimport kotlin.experimental.and\n\nopen class ShadowPluginHelper {\n    companion object {\n        fun getFileMD5(file: File): String? {\n            if (!file.isFile) {\n                return null\n            }\n\n            val buffer = ByteArray(1024)\n            var len: Int\n            var inStream: FileInputStream? = null\n            val digest = MessageDigest.getInstance(\"MD5\")\n            try {\n                inStream = FileInputStream(file)\n                do {\n                    len = inStream.read(buffer, 0, 1024)\n                    if (len != -1) {\n                        digest.update(buffer, 0, len)\n                    }\n                } while (len != -1)\n            } catch (e: Exception) {\n                e.printStackTrace()\n                return null\n            } finally {\n                inStream?.close()\n            }\n            return bytes2HexStr(digest.digest())\n        }\n\n        private fun bytes2HexStr(bytes: ByteArray?): String {\n            val HEX_ARRAY = \"0123456789ABCDEF\".toCharArray()\n            if (bytes == null || bytes.isEmpty()) {\n                return \"\"\n            }\n\n            val buf = CharArray(2 * bytes.size)\n            try {\n                for (i in bytes.indices) {\n                    var b = bytes[i]\n                    buf[2 * i + 1] = HEX_ARRAY[(b and 0xF).toInt()]\n                    b = b.toInt().ushr(4).toByte()\n                    buf[2 * i + 0] = HEX_ARRAY[(b and 0xF).toInt()]\n                }\n            } catch (e: Exception) {\n                return \"\"\n            }\n\n            return String(buf)\n        }\n\n        fun getRuntimeApkFile(\n            project: Project,\n            buildType: PluginBuildType,\n            checkExist: Boolean\n        ): File {\n            val packagePlugin = project.extensions.findByName(\"packagePlugin\")\n            val extension = packagePlugin as PackagePluginExtension\n\n            val splitList = buildType.runtimeApkConfig.second.split(\":\")\n            val runtimeFileParent =\n                splitList[splitList.lastIndex].replace(\"assemble\", \"\").toLowerCase()\n            val runtimeApkName: String = buildType.runtimeApkConfig.first\n            val runtimeFile = File(\n                \"${project.rootDir}\" +\n                        \"/${extension.runtimeApkProjectPath}/build/outputs/apk/$runtimeFileParent/$runtimeApkName\"\n            )\n            if (checkExist && !runtimeFile.exists()) {\n                throw IllegalArgumentException(runtimeFile.absolutePath + \" , runtime file not exist...\")\n            }\n            project.logger.info(\"runtimeFile = $runtimeFile\")\n            return runtimeFile\n        }\n\n        fun getLoaderApkFile(\n            project: Project,\n            buildType: PluginBuildType,\n            checkExist: Boolean\n        ): File {\n            val packagePlugin = project.extensions.findByName(\"packagePlugin\")\n            val extension = packagePlugin as PackagePluginExtension\n\n            val loaderApkName: String = buildType.loaderApkConfig.first\n            val splitList = buildType.loaderApkConfig.second.split(\":\")\n            val loaderFileParent =\n                splitList[splitList.lastIndex].replace(\"assemble\", \"\").toLowerCase()\n            val loaderFile = File(\n                \"${project.rootDir}\" +\n                        \"/${extension.loaderApkProjectPath}/build/outputs/apk/$loaderFileParent/$loaderApkName\"\n            )\n            if (checkExist && !loaderFile.exists()) {\n                throw IllegalArgumentException(loaderFile.absolutePath + \" , loader file not exist...\")\n            }\n            project.logger.info(\"loaderFile = $loaderFile\")\n            return loaderFile\n\n        }\n\n        fun getPluginFile(\n            project: Project,\n            pluginConfig: PluginApkConfig,\n            checkExist: Boolean\n        ): File {\n            val pluginFile = File(project.rootDir, pluginConfig.apkPath)\n            if (checkExist && !pluginFile.exists()) {\n                throw IllegalArgumentException(pluginFile.absolutePath + \" , plugin file not exist...\")\n            }\n            project.logger.info(\"pluginFile = $pluginFile\")\n            return pluginFile\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/extensions/PackagePluginExtension.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle.extensions\n\nimport com.tencent.shadow.core.gradle.ShadowPluginHelper\nimport groovy.lang.Closure\nimport org.gradle.api.NamedDomainObjectContainer\nimport org.gradle.api.Project\nimport org.json.simple.JSONArray\nimport org.json.simple.JSONObject\nimport java.io.File\nimport java.util.*\n\nopen class PackagePluginExtension {\n\n    var loaderApkProjectPath = \"\"\n    var runtimeApkProjectPath = \"\"\n\n    var archivePrefix = \"\"\n    var archiveSuffix = \"\"\n    var destinationDir = \"\"\n\n    var uuid = \"\"\n    var version: Int = 0\n    var uuidNickName = \"\"\n    var compactVersion: Array<Int> = emptyArray()\n\n    var buildTypes: NamedDomainObjectContainer<PluginBuildType>\n\n    constructor(project: Project) {\n        buildTypes = project.container(PluginBuildType::class.java)\n        buildTypes.all {\n            it.pluginApks = project.container(PluginApkConfig::class.java)\n        }\n    }\n\n    fun pluginTypes(closure: Closure<PluginBuildType>) {\n        buildTypes.configure(closure)\n    }\n\n    fun toJson(\n        project: Project,\n        loaderApkName: String,\n        runtimeApkName: String,\n        buildType: PluginBuildType\n    ): JSONObject {\n        val json = JSONObject()\n\n        if (loaderApkName.isNotEmpty()) {\n            //Json文件中 plugin-loader部分信息\n            val pluginLoaderObj = JSONObject()\n            pluginLoaderObj[\"apkName\"] = loaderApkName\n            val loaderFile = ShadowPluginHelper.getLoaderApkFile(project, buildType, true)\n            pluginLoaderObj[\"hash\"] = ShadowPluginHelper.getFileMD5(loaderFile)\n            json[\"pluginLoader\"] = pluginLoaderObj\n        }\n\n\n        if (runtimeApkName.isNotEmpty()) {\n            //Json文件中 plugin-runtime部分信息\n            val runtimeObj = JSONObject()\n            runtimeObj[\"apkName\"] = runtimeApkName\n            val runtimeFile = ShadowPluginHelper.getRuntimeApkFile(project, buildType, true)\n            runtimeObj[\"hash\"] = ShadowPluginHelper.getFileMD5(runtimeFile)\n            json[\"runtime\"] = runtimeObj\n        }\n\n\n        //Json文件中 plugin部分信息\n        val jsonArr = JSONArray()\n        for (i in buildType.pluginApks) {\n            val pluginObj = JSONObject()\n            pluginObj[\"businessName\"] = i.businessName\n            pluginObj[\"partKey\"] = i.partKey\n            pluginObj[\"apkName\"] = File(i.apkPath).name\n            pluginObj[\"hash\"] =\n                ShadowPluginHelper.getFileMD5(ShadowPluginHelper.getPluginFile(project, i, true))\n            if (i.dependsOn.isNotEmpty()) {\n                val dependsOnJson = JSONArray()\n                for (k in i.dependsOn) {\n                    dependsOnJson.add(k)\n                }\n                pluginObj[\"dependsOn\"] = dependsOnJson\n            }\n            if (i.hostWhiteList.isNotEmpty()) {\n                val hostWhiteListJson = JSONArray()\n                for (k in i.hostWhiteList) {\n                    hostWhiteListJson.add(k)\n                }\n                pluginObj[\"hostWhiteList\"] = hostWhiteListJson\n            }\n            jsonArr.add(pluginObj)\n        }\n        json[\"plugins\"] = jsonArr\n\n\n        //Config.json版本号\n        if (version > 0) {\n            json[\"version\"] = version\n        } else {\n            json[\"version\"] = 1\n        }\n\n\n        //uuid UUID_NickName\n        val uuid = \"${project.rootDir}\" + \"/build/uuid.txt\"\n        val uuidFile = File(uuid)\n        when {\n            uuidFile.exists() -> {\n                json[\"UUID\"] = uuidFile.readText()\n                project.logger.info(\"uuid = \" + json[\"UUID\"] + \" 由文件生成\")\n            }\n            this.uuid.isEmpty() -> {\n                json[\"UUID\"] = UUID.randomUUID().toString().toUpperCase()\n                project.logger.info(\"uuid = \" + json[\"UUID\"] + \" 随机生成\")\n            }\n            else -> {\n                json[\"UUID\"] = this.uuid\n                project.logger.info(\"uuid = \" + json[\"UUID\"] + \" 由配置生成\")\n            }\n        }\n\n        if (uuidNickName.isNotEmpty()) {\n            json[\"UUID_NickName\"] = uuidNickName\n        } else {\n            json[\"UUID_NickName\"] = \"1.0\"\n        }\n\n        if (compactVersion.isNotEmpty()) {\n            val jsonArray = JSONArray()\n            for (i in compactVersion) {\n                jsonArray.add(i)\n            }\n            json[\"compact_version\"] = jsonArray\n        }\n        return json\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/extensions/PluginApkConfig.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle.extensions\n\nopen class PluginApkConfig {\n\n    var name = \"\"\n\n    var partKey = \"\"\n\n    /**\n     * 业务名（空字符串表示同宿主相同业务）\n     */\n    var businessName = \"\"\n\n    var apkPath = \"\"\n    var buildTask = \"\"\n    var dependsOn: Array<String> = emptyArray()\n    var hostWhiteList: Array<String> = emptyArray()\n\n    constructor(name: String) {\n        this.name = name\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/main/kotlin/com/tencent/shadow/core/gradle/extensions/PluginBuildType.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle.extensions\n\nimport groovy.lang.Closure\nimport groovy.lang.Tuple2\nimport org.gradle.api.NamedDomainObjectContainer\n\nopen class PluginBuildType {\n\n    var name = \"\"\n\n    var loaderApkConfig: Tuple2<String, String> = Tuple2(\"\", \"\")\n    var runtimeApkConfig: Tuple2<String, String> = Tuple2(\"\", \"\")\n    lateinit var pluginApks: NamedDomainObjectContainer<PluginApkConfig>\n\n    constructor(name: String) {\n        this.name = name\n    }\n\n    fun pluginApks(closure: Closure<PluginApkConfig>) {\n        pluginApks.configure(closure)\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageMultiPluginTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport org.gradle.testkit.runner.GradleRunner\nimport org.gradle.testkit.runner.TaskOutcome\nimport org.json.simple.JSONArray\nimport org.json.simple.JSONObject\nimport org.json.simple.parser.JSONParser\nimport org.junit.Assert\nimport org.junit.Test\nimport java.io.File\nimport java.util.zip.ZipFile\n\n/**\n *  测试打包多个工程的插件包 第一个插件包包含loader、runtime、插件1、config.json的插件包  第二个插件包 包含 插件2、config.json\n *  其中插件包1 中的json文件中的uuid 需要和 插件包2 中json文件的uuid 一样\n * ./gradlew -p projects/sdk/core :gradle-plugin:test --tests com.tencent.shadow.core.gradle.PackageMultiPluginTest.testPackageMultiPlugin\n */\nclass PackageMultiPluginTest {\n\n    @Test\n    fun testPackageMultiPlugin() {\n        GradleRunner.create()\n            .withProjectDir(ROOT_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\"clean\")\n            .build()\n\n        val result = GradleRunner.create()\n            .withProjectDir(ROOT_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\n                listOf(\n                    \"-xgeneratePluginDebugPluginManifest\",\n                    \"-Pdisable_shadow_transform=true\",\n                    \":plugin1:PackageMultiPlugin\"\n                )\n            )\n            .build()\n\n        val outcome = result.task(\":plugin1:PackageMultiPlugin\")!!.outcome\n\n        Assert.assertEquals(TaskOutcome.SUCCESS, outcome)\n\n        assertJson()\n\n        assertFile()\n    }\n\n    private fun assertFile() {\n        val zipFile = ZipFile(ROOT_PROJECT_DIR.absolutePath + \"/build/plugin-debug.zip\")\n        val zipFileNames = mutableSetOf<String>()\n        zipFileNames.add(\"config.json\")\n        zipFileNames.add(\"plugin1-plugin-debug.apk\")\n        zipFileNames.add(\"loader-debug.apk\")\n        zipFileNames.add(\"runtime-debug.apk\")\n\n        var entries = zipFile.entries()\n        Assert.assertEquals(4, zipFile.size())\n\n        for (i in entries) {\n            zipFileNames.remove(i.name)\n        }\n        Assert.assertEquals(0, zipFileNames.size)\n\n        val case2ZipFile = ZipFile(ROOT_PROJECT_DIR.absolutePath + \"/build/plugin-plugin2Debug.zip\")\n        zipFileNames.add(\"config.json\")\n        zipFileNames.add(\"plugin2-plugin-debug.apk\")\n\n        entries = case2ZipFile.entries()\n        Assert.assertEquals(2, case2ZipFile.size())\n\n        for (i in entries) {\n            zipFileNames.remove(i.name)\n        }\n        Assert.assertEquals(0, zipFileNames.size)\n    }\n\n    private fun assertJson() {\n        val jsonFile =\n            File(PLUGIN1_PROJECT_DIR, \"build/intermediates/generatePluginConfig/debug/config.json\")\n        val json = JSONParser().parse(jsonFile.bufferedReader()) as JSONObject\n        Assert.assertEquals(4L, json[\"version\"])\n\n        Assert.assertEquals(\"1.1.5\", json[\"UUID_NickName\"])\n\n        val compactVersionArr: JSONArray = json[\"compact_version\"] as JSONArray\n        Assert.assertEquals(1L, compactVersionArr[0] as Long)\n\n        val loaderJson = json[\"pluginLoader\"] as JSONObject\n        Assert.assertEquals(\"loader-debug.apk\", loaderJson[\"apkName\"])\n        Assert.assertNotNull(loaderJson[\"hash\"])\n\n        val runtimeJson = json[\"runtime\"] as JSONObject\n        Assert.assertEquals(\"runtime-debug.apk\", runtimeJson[\"apkName\"])\n        Assert.assertNotNull(runtimeJson[\"hash\"])\n\n        val pluginsJson = json[\"plugins\"] as JSONArray\n        val pluginJson = pluginsJson[0] as JSONObject\n        Assert.assertEquals(\"plugin1\", pluginJson[\"partKey\"])\n        Assert.assertEquals(\"plugin1-plugin-debug.apk\", pluginJson[\"apkName\"])\n        val dependsOnJson = pluginJson[\"dependsOn\"] as JSONArray\n        Assert.assertEquals(2, dependsOnJson.size)\n        Assert.assertNotNull(pluginJson[\"hash\"])\n\n        val hostWhiteListJson = pluginJson[\"hostWhiteList\"] as JSONArray\n        Assert.assertEquals(2, hostWhiteListJson.size)\n\n        val case2JsonFile = File(\n            PLUGIN2_PROJECT_DIR,\n            \"/build/intermediates/generatePluginConfig/plugin2Debug/config.json\"\n        )\n        val case2Json = JSONParser().parse(case2JsonFile.bufferedReader()) as JSONObject\n        Assert.assertEquals(case2Json[\"UUID\"], json[\"UUID\"])\n    }\n\n    companion object {\n        val ROOT_PROJECT_DIR = File(\"src/test/testProjects/case1\")\n        val PLUGIN1_PROJECT_DIR = File(\"src/test/testProjects/case1/plugin1\")\n        val PLUGIN2_PROJECT_DIR = File(\"src/test/testProjects/case1/plugin2\")\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackageOnlyPluginTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport org.gradle.testkit.runner.GradleRunner\nimport org.gradle.testkit.runner.TaskOutcome\nimport org.json.simple.JSONArray\nimport org.json.simple.JSONObject\nimport org.json.simple.parser.JSONParser\nimport org.junit.Assert\nimport org.junit.Test\nimport java.io.File\nimport java.util.zip.ZipFile\n\n/**\n *  测试打包只包含、插件1、config.json的插件包\n * ./gradlew -p projects/sdk/core :gradle-plugin:test --tests com.tencent.shadow.core.gradle.PackageOnlyPluginTest.testCase1PackageOnlyApk\n */\nclass PackageOnlyPluginTest {\n\n    @Test\n    fun testCase1PackageOnlyApk() {\n        GradleRunner.create()\n            .withProjectDir(PLUGIN1_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\"clean\")\n            .build()\n\n        val result = GradleRunner.create()\n            .withProjectDir(PLUGIN1_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\n                listOf(\n                    \"-xgeneratePluginDebugPluginManifest\",\n                    \"-Pdisable_shadow_transform=true\",\n                    \":plugin1:packageOnlyApkPlugin\"\n                )\n            )\n            .build()\n\n        val outcome = result.task(\":plugin1:packageOnlyApkPlugin\")!!.outcome\n\n        Assert.assertEquals(TaskOutcome.SUCCESS, outcome)\n\n        val jsonFile = File(\n            PLUGIN1_PROJECT_DIR,\n            \"build/intermediates/generatePluginConfig/onlyApk/config.json\"\n        )\n        val json = JSONParser().parse(jsonFile.bufferedReader()) as JSONObject\n        assertJson(json)\n\n        val zipFile = ZipFile(ROOT_PROJECT_DIR.absolutePath + \"/build/plugin-onlyApk.zip\")\n        assertFile(zipFile)\n    }\n\n    private fun assertFile(zipFile: ZipFile) {\n        val zipFileNames = mutableSetOf<String>()\n        zipFileNames.add(\"config.json\")\n        zipFileNames.add(\"plugin1-plugin-debug.apk\")\n\n        val entries = zipFile.entries()\n        Assert.assertEquals(2, zipFile.size())\n\n        for (i in entries) {\n            zipFileNames.remove(i.name)\n        }\n        Assert.assertEquals(0, zipFileNames.size)\n\n    }\n\n    private fun assertJson(json: JSONObject) {\n        Assert.assertEquals(4L, json[\"version\"])\n\n        Assert.assertEquals(\"1234567890\", json[\"UUID\"])\n\n        Assert.assertEquals(\"1.1.5\", json[\"UUID_NickName\"])\n\n        val compactVersionArr: JSONArray = json[\"compact_version\"] as JSONArray\n        Assert.assertEquals(1L, compactVersionArr[0] as Long)\n\n\n        val pluginsJson = json[\"plugins\"] as JSONArray\n        val pluginJson = pluginsJson[0] as JSONObject\n        Assert.assertEquals(\"plugin1\", pluginJson[\"partKey\"])\n        Assert.assertEquals(\"plugin1-plugin-debug.apk\", pluginJson[\"apkName\"])\n        val dependsOnJson = pluginJson[\"dependsOn\"] as JSONArray\n        Assert.assertEquals(2, dependsOnJson.size)\n        Assert.assertNotNull(pluginJson[\"hash\"])\n\n        val hostWhiteList = pluginJson[\"hostWhiteList\"] as JSONArray\n        Assert.assertEquals(2, hostWhiteList.size)\n    }\n\n    companion object {\n        val ROOT_PROJECT_DIR = File(\"src/test/testProjects/case1\")\n        val PLUGIN1_PROJECT_DIR = File(\"src/test/testProjects/case1/plugin1\")\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/kotlin/com/tencent/shadow/core/gradle/PackagePluginTaskTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.gradle\n\nimport org.gradle.testkit.runner.GradleRunner\nimport org.gradle.testkit.runner.TaskOutcome.SUCCESS\nimport org.json.simple.JSONArray\nimport org.json.simple.JSONObject\nimport org.json.simple.parser.JSONParser\nimport org.junit.Assert\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertNotNull\nimport org.junit.Test\nimport java.io.File\nimport java.util.zip.ZipFile\n\n/**\n *   测试打包包含loader、runtime、插件1、config.json的插件包\n *  ./gradlew -p projects/sdk/core :gradle-plugin:test --tests com.tencent.shadow.core.gradle.PackagePluginTaskTest.testCase1PackageDebugPlugin\n */\nclass PackagePluginTaskTest {\n\n    @Test\n    fun testCase1PackageDebugPlugin() {\n        GradleRunner.create()\n            .withProjectDir(ROOT_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\"clean\")\n            .build()\n\n        val result = GradleRunner.create()\n            .withProjectDir(ROOT_PROJECT_DIR)\n            .withPluginClasspath()\n            .withArguments(\n                listOf(\n                    \"-xgeneratePluginDebugPluginManifest\",\n                    \"-Pdisable_shadow_transform=true\",\n                    \":plugin1:packageDebugPlugin\"\n                )\n            )\n            .build()\n\n        val outcome = result.task(\":plugin1:packageDebugPlugin\")!!.outcome\n\n        assertEquals(SUCCESS, outcome)\n\n        val jsonFile =\n            File(PLUGIN1_PROJECT_DIR, \"build/intermediates/generatePluginConfig/debug/config.json\")\n        val json = JSONParser().parse(jsonFile.bufferedReader()) as JSONObject\n        assertJson(json)\n\n        val zipFile = ZipFile(ROOT_PROJECT_DIR.absolutePath + \"/build/plugin-debug.zip\")\n        assertFile(zipFile)\n    }\n\n    private fun assertFile(zipFile: ZipFile) {\n        val zipFileNames = mutableSetOf<String>()\n        zipFileNames.add(\"config.json\")\n        zipFileNames.add(\"plugin1-plugin-debug.apk\")\n        zipFileNames.add(\"loader-debug.apk\")\n        zipFileNames.add(\"runtime-debug.apk\")\n\n        val entries = zipFile.entries()\n        assertEquals(4, zipFile.size())\n\n        for (i in entries) {\n            zipFileNames.remove(i.name)\n        }\n        assertEquals(0, zipFileNames.size)\n\n    }\n\n    private fun assertJson(json: JSONObject) {\n        assertEquals(4L, json[\"version\"])\n\n        assertEquals(\"1234567890\", json[\"UUID\"])\n\n        assertEquals(\"1.1.5\", json[\"UUID_NickName\"])\n\n        val compactVersionArr: JSONArray = json[\"compact_version\"] as JSONArray\n        assertEquals(1L, compactVersionArr[0] as Long)\n\n        val loaderJson = json[\"pluginLoader\"] as JSONObject\n        assertEquals(\"loader-debug.apk\", loaderJson[\"apkName\"])\n        assertNotNull(loaderJson[\"hash\"])\n\n        val runtimeJson = json[\"runtime\"] as JSONObject\n        assertEquals(\"runtime-debug.apk\", runtimeJson[\"apkName\"])\n        assertNotNull(runtimeJson[\"hash\"])\n\n        val pluginsJson = json[\"plugins\"] as JSONArray\n        val pluginJson = pluginsJson[0] as JSONObject\n        assertEquals(\"plugin1\", pluginJson[\"partKey\"])\n        assertEquals(\"plugin1\", pluginJson[\"businessName\"])\n        assertEquals(\"plugin1-plugin-debug.apk\", pluginJson[\"apkName\"])\n        val dependsOnJson = pluginJson[\"dependsOn\"] as JSONArray\n        assertEquals(2, dependsOnJson.size)\n        assertNotNull(pluginJson[\"hash\"])\n\n        val hostWhiteList = pluginJson[\"hostWhiteList\"] as JSONArray\n        Assert.assertEquals(2, hostWhiteList.size)\n    }\n\n    companion object {\n        val ROOT_PROJECT_DIR = File(\"src/test/testProjects/case1\")\n        val PLUGIN1_PROJECT_DIR = File(\"src/test/testProjects/case1/plugin1\")\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.gradletasknamecache"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/build.gradle",
    "content": "buildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../../../../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n    }\n}\n\nplugins {\n    id 'com.android.application'\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n}\n\next.disable_shadow_transform = true\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.case1\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n}\n\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/loader/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n}\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.case1\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/loader/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"loader\" />\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin1/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.gradletasknamecache"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin1/build.gradle",
    "content": "buildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../../../../../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n    }\n}\n\nplugins {\n    id 'com.android.application'\n    id 'com.tencent.shadow.plugin'\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n}\n\next.disable_shadow_transform = true\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.plugin1\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n}\n\nshadow {\n    packagePlugin {\n        pluginTypes {\n            debug {\n                loaderApkConfig = new Tuple2('loader-debug.apk', ':loader:assembleDebug')\n                runtimeApkConfig = new Tuple2('runtime-debug.apk', ':runtime:assembleDebug')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'plugin1'\n                        partKey = 'plugin1'\n                        buildTask = ':plugin1:assemblePluginDebug'\n                        apkPath = 'plugin1/build/outputs/apk/plugin/debug/plugin1-plugin-debug.apk'\n                        dependsOn = ['Core', 'Base']\n                        hostWhiteList = [\"androidx.test.espresso\",\n                                         \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"]\n                    }\n                }\n            }\n\n            onlyApk {\n                pluginApks {\n                    pluginApk1 {\n                        partKey = 'plugin1'\n                        buildTask = ':plugin1:assemblePluginDebug'\n                        apkPath = 'plugin1/build/outputs/apk/plugin/debug/plugin1-plugin-debug.apk'\n                        dependsOn = ['Core', 'Base']\n                        hostWhiteList = [\"androidx.test.espresso\",\n                                         \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"]\n                    }\n                }\n            }\n        }\n\n        loaderApkProjectPath = 'loader'\n\n        runtimeApkProjectPath = 'runtime'\n\n        uuid = '1234567890'\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}\n\ntask genUUID()  {\n    doFirst {\n        def uuidFile = file(rootProject.projectDir.absolutePath + '/build/uuid.txt')\n        uuidFile.getParentFile().mkdirs()\n        BufferedWriter writer = new BufferedWriter(new FileWriter(uuidFile))\n        writer.write(UUID.randomUUID().toString().toUpperCase())\n        writer.flush()\n        writer.close()\n    }\n}\n\ntask PackageMultiPlugin(dependsOn: ['genUUID', 'packageDebugPlugin', ':plugin2:packagePlugin2DebugPlugin']) {\n    doLast {\n        file(rootProject.projectDir.absolutePath + '/build/uuid.txt').delete()\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin1/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"app\" />\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin2/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.gradletasknamecache"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin2/build.gradle",
    "content": "buildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../../../../../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n    }\n}\n\nplugins {\n    id 'com.android.application'\n    id 'com.tencent.shadow.plugin'\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n}\n\next.disable_shadow_transform = true\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.case1\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n}\n\nshadow {\n    packagePlugin {\n        pluginTypes {\n            plugin2Debug {\n                pluginApks {\n                    pluginApk1 {\n                        partKey = 'plugin2'\n                        buildTask = ':plugin2:assemblePluginDebug'\n                        //这里因为单元测试时，会把项目根目录设置成case1的根目录\n                        apkPath = 'plugin2/build/outputs/apk/plugin/debug/plugin2-plugin-debug.apk'\n                        dependsOn = ['Core', 'Base']\n                        hostWhiteList = [\"androidx.test.espresso\",\n                                         \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"]\n                    }\n                }\n            }\n        }\n\n        uuid = '1234567890'\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/plugin2/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"app\" />\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/runtime/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n}\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.case1\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/runtime/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"runtime\" />\n"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/settings.gradle",
    "content": "rootProject.name = 'case1'\ninclude 'loader', 'runtime', 'plugin1', 'plugin2'"
  },
  {
    "path": "projects/sdk/core/gradle-plugin/src/test/testProjects/case1/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"app\" />\n"
  },
  {
    "path": "projects/sdk/core/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\n"
  },
  {
    "path": "projects/sdk/core/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sdk/core/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sdk/core/load-parameters/.gitignore",
    "content": "/build\n*.iml\n"
  },
  {
    "path": "projects/sdk/core/load-parameters/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n"
  },
  {
    "path": "projects/sdk/core/load-parameters/src/main/java/com/tencent/shadow/core/load_parameters/LoadParameters.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.load_parameters;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * Loader加载插件的输入参数结构体\n * <p>\n * 这个类不能用Kotlin写是因为这个类可能会由非Kotlin写的代码new出来，\n * 而Loader打包的kotlin运行时可能连同Loader一起在一个独立的ClassLoader中。\n * 如果这个类用Kotlin写，就要求构造这个类对象的代码具有Kotlin运行时。\n *\n * @author cubershi\n */\npublic class LoadParameters implements Parcelable {\n    public final String businessName;\n    public final String partKey;\n    public final String[] dependsOn;\n    public final String[] hostWhiteList;\n\n    public LoadParameters(String businessName, String partKey, String[] dependsOn, String[] hostWhiteList) {\n        this.businessName = businessName;\n        this.partKey = partKey;\n        this.dependsOn = dependsOn;\n        this.hostWhiteList = hostWhiteList;\n    }\n\n    public LoadParameters(Parcel in) {\n        businessName = in.readString();\n        partKey = in.readString();\n        dependsOn = in.createStringArray();\n        hostWhiteList = in.createStringArray();\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(businessName);\n        dest.writeString(partKey);\n        dest.writeStringArray(dependsOn);\n        dest.writeStringArray(hostWhiteList);\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<LoadParameters> CREATOR = new Creator<LoadParameters>() {\n        @Override\n        public LoadParameters createFromParcel(Parcel in) {\n            return new LoadParameters(in);\n        }\n\n        @Override\n        public LoadParameters[] newArray(int size) {\n            return new LoadParameters[size];\n        }\n    };\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/loader/build.gradle",
    "content": "import com.tencent.shadow.coding.code_generator.ActivityCodeGenerator\nimport com.tencent.shadow.coding.common_jar_settings.AndroidJar\n\nbuildscript {\n    dependencies {\n        classpath 'com.tencent.shadow.coding:android-jar'\n        classpath 'com.tencent.shadow.coding:code-generator'\n    }\n}\n\napply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\napply plugin: 'kotlin'\n\njava {\n    sourceSets {\n        main.java.srcDirs += 'build/generated/sources/code-generator'\n    }\n}\n\ncompileKotlin {\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n        noJdk = true\n        noStdlib = true\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    testImplementation \"junit:junit:$junit_version\"\n\n    implementation 'com.tencent.shadow.coding:java-build-config'\n    api project(':runtime')\n    compileOnly project(':activity-container')\n    compileOnly project(':common')\n    api project(':load-parameters')\n    compileOnly files(AndroidJar.ANDROID_JAR_PATH)\n}\n\ndef generateCode = tasks.register('generateCode') {\n    def outputDir = layout.buildDirectory.dir('generated/sources/code-generator')\n    outputs.dir(outputDir)\n            .withPropertyName('outputDir')\n    doLast {\n        ActivityCodeGenerator codeGenerator = new ActivityCodeGenerator()\n        codeGenerator.generate(outputDir.get().getAsFile(), \"loader\")\n    }\n}\n\ncompileJava.dependsOn(generateCode)\ncompileKotlin.dependsOn(generateCode)\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/ShadowPluginLoader.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Parcel\nimport com.tencent.shadow.core.common.InstalledApk\nimport com.tencent.shadow.core.common.LoggerFactory\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.loader.blocs.LoadPluginBloc\nimport com.tencent.shadow.core.loader.delegates.*\nimport com.tencent.shadow.core.loader.exceptions.LoadPluginException\nimport com.tencent.shadow.core.loader.infos.PluginParts\nimport com.tencent.shadow.core.loader.managers.ComponentManager\nimport com.tencent.shadow.core.loader.managers.PluginContentProviderManager\nimport com.tencent.shadow.core.loader.managers.PluginServiceManager\nimport com.tencent.shadow.core.runtime.UriConverter\nimport com.tencent.shadow.core.runtime.container.*\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.Executors\nimport java.util.concurrent.Future\nimport java.util.concurrent.locks.ReentrantLock\nimport kotlin.concurrent.withLock\n\nabstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, DI,\n    ContentProviderDelegateProvider {\n\n    protected val mExecutorService = Executors.newCachedThreadPool()\n\n    open val delegateProviderKey: String = DelegateProviderHolder.DEFAULT_KEY\n\n    /**\n     * loadPlugin方法是在子线程被调用的。而getHostActivityDelegate方法是在主线程被调用的。\n     * 两个方法需要传递数据（主要是PluginParts），因此需要同步。\n     */\n    private val mLock = ReentrantLock()\n\n    /**\n     * 多插件Map\n     * key: partKey\n     * value: PluginParts\n     * @GuardedBy(\"mLock\")\n     */\n    private val mPluginPartsMap = hashMapOf<String, PluginParts>()\n\n\n    lateinit var mComponentManager: ComponentManager\n\n    /**\n     * @GuardedBy(\"mLock\")\n     */\n    abstract fun getComponentManager(): ComponentManager\n\n    private lateinit var mPluginServiceManager: PluginServiceManager\n\n    private val mPluginContentProviderManager: PluginContentProviderManager =\n        PluginContentProviderManager()\n\n    private val mPluginServiceManagerLock = ReentrantLock()\n\n    private val mHostAppContext: Context = hostAppContext\n\n    private val mUiHandler = Handler(Looper.getMainLooper())\n\n    companion object {\n        private val mLogger = LoggerFactory.getLogger(ShadowPluginLoader::class.java)\n    }\n\n    init {\n        UriConverter.setUriParseDelegate(mPluginContentProviderManager)\n    }\n\n    fun getPluginServiceManager(): PluginServiceManager {\n        mPluginServiceManagerLock.withLock {\n            return mPluginServiceManager\n        }\n\n    }\n\n    fun getPluginParts(partKey: String): PluginParts? {\n        mLock.withLock {\n            return mPluginPartsMap[partKey]\n        }\n    }\n\n    fun getAllPluginPart(): HashMap<String, PluginParts> {\n        mLock.withLock {\n            return mPluginPartsMap\n        }\n    }\n\n    fun onCreate() {\n        mComponentManager = getComponentManager()\n        mComponentManager.setPluginContentProviderManager(mPluginContentProviderManager)\n    }\n\n    fun callApplicationOnCreate(partKey: String) {\n        fun realAction() {\n            val pluginParts = getPluginParts(partKey)\n            pluginParts?.let {\n                val application = pluginParts.application\n                application.attachBaseContext(mHostAppContext)\n                mPluginContentProviderManager.createContentProviderAndCallOnCreate(\n                    application, partKey, pluginParts\n                )\n                application.onCreate()\n            }\n        }\n        if (isUiThread()) {\n            realAction()\n        } else {\n            val waitUiLock = CountDownLatch(1)\n            mUiHandler.post {\n                realAction()\n                waitUiLock.countDown()\n            }\n            waitUiLock.await();\n        }\n    }\n\n    @Throws(LoadPluginException::class)\n    open fun loadPlugin(\n        installedApk: InstalledApk\n    ): Future<*> {\n        val loadParameters = installedApk.getLoadParameters()\n        if (mLogger.isInfoEnabled) {\n            mLogger.info(\"start loadPlugin\")\n        }\n        // 在这里初始化PluginServiceManager\n        mPluginServiceManagerLock.withLock {\n            if (!::mPluginServiceManager.isInitialized) {\n                mPluginServiceManager = PluginServiceManager(this, mHostAppContext)\n            }\n\n            mComponentManager.setPluginServiceManager(mPluginServiceManager)\n        }\n\n        return LoadPluginBloc.loadPlugin(\n            mExecutorService,\n            mComponentManager,\n            mLock,\n            mPluginPartsMap,\n            mHostAppContext,\n            installedApk,\n            loadParameters\n        )\n    }\n\n    override fun getHostActivityDelegate(aClass: Class<out HostActivityDelegator>): HostActivityDelegate {\n        return if (HostNativeActivityDelegator::class.java.isAssignableFrom(aClass)) {\n            ShadowNativeActivityDelegate(this)\n        } else {\n            ShadowActivityDelegate(this)\n        }\n    }\n\n\n    override fun getHostContentProviderDelegate(): HostContentProviderDelegate {\n        return ShadowContentProviderDelegate(mPluginContentProviderManager)\n    }\n\n    override fun inject(delegate: ShadowDelegate, partKey: String) {\n        mLock.withLock {\n            val pluginParts = mPluginPartsMap[partKey]\n            if (pluginParts == null) {\n                throw IllegalStateException(\"partKey==${partKey}在map中找不到。此时map：${mPluginPartsMap}\")\n            } else {\n                delegate.inject(pluginParts.appComponentFactory)\n                delegate.inject(pluginParts.application)\n                delegate.inject(pluginParts.classLoader)\n                delegate.inject(pluginParts.resources)\n                delegate.inject(mComponentManager)\n            }\n        }\n    }\n\n    fun InstalledApk.getLoadParameters(): LoadParameters {\n        val parcel = Parcel.obtain()\n        parcel.unmarshall(parcelExtras, 0, parcelExtras.size)\n        parcel.setDataPosition(0)\n        val loadParameters = LoadParameters(parcel)\n        parcel.recycle()\n        return loadParameters\n    }\n\n    private fun isUiThread(): Boolean {\n        return Thread.currentThread() === Looper.getMainLooper().thread\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CheckPackageNameBloc.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.blocs\n\nimport android.content.Context\nimport com.tencent.shadow.core.loader.exceptions.ParsePluginApkException\nimport com.tencent.shadow.core.runtime.PluginManifest\n\nobject CheckPackageNameBloc {\n    @Throws(ParsePluginApkException::class)\n    fun check(\n        pluginManifest: PluginManifest,\n        hostAppContext: Context\n    ) {\n        if (pluginManifest.applicationPackageName != hostAppContext.packageName) {\n            /*\n            要求插件和宿主包名一致有两方面原因：\n            1.正常的构建过程中，aapt会将包名写入到arsc文件中。插件正常安装运行时，如果以\n            android.content.Context.getPackageName为参数传给\n            android.content.res.Resources.getIdentifier方法，可以正常获取到资源。但是在插件环境运行时，\n            Context.getPackageName会得到宿主的packageName，则getIdentifier方法不能正常获取到资源。为此，\n            一个可选的办法是继承Resources，覆盖getIdentifier方法。但是Resources的构造器已经被标记为\n            @Deprecated了，未来可能会不可用，因此不首选这个方法。\n\n            2.Android系统，更多情况下是OEM修改的Android系统，会在我们的context上调用getPackageName或者\n            getOpPackageName等方法，然后将这个packageName跨进程传递做它用。系统的其他代码会以这个packageName\n            去PackageManager中查询权限等信息。如果插件使用自己的包名，就需要在Context的getPackageName等实现中\n            new Throwable()，然后判断调用来源以决定返回自己的包名还是插件的包名。但是如果保持采用宿主的包名，则没有\n            这个烦恼。\n\n            我们也可以始终认为Shadow App是宿主的扩展代码，使用是宿主的一部分，那么采用宿主的包名就是理所应当的了。\n             */\n            throw ParsePluginApkException(\"插件和宿主包名不一致。宿主:${hostAppContext.packageName} 插件:${pluginManifest.applicationPackageName}\")\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateApplicationBloc.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.blocs\n\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.content.res.Resources\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader\nimport com.tencent.shadow.core.loader.exceptions.CreateApplicationException\nimport com.tencent.shadow.core.loader.managers.ComponentManager\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport com.tencent.shadow.core.runtime.ShadowAppComponentFactory\nimport com.tencent.shadow.core.runtime.ShadowApplication\n\n/**\n * 初始化插件Application类\n *\n * @author cubershi\n */\nobject CreateApplicationBloc {\n    @Throws(CreateApplicationException::class)\n    fun createShadowApplication(\n        pluginClassLoader: PluginClassLoader,\n        loadParameters: LoadParameters,\n        pluginManifest: PluginManifest,\n        resources: Resources,\n        hostAppContext: Context,\n        componentManager: ComponentManager,\n        pluginApplicationInfo: ApplicationInfo,\n        appComponentFactory: ShadowAppComponentFactory\n    ): ShadowApplication {\n        try {\n            val appClassName = pluginManifest.applicationClassName\n                ?: ShadowApplication::class.java.name\n            val shadowApplication =\n                appComponentFactory.instantiateApplication(pluginClassLoader, appClassName)\n            val partKey = loadParameters.partKey\n            shadowApplication.setPluginResources(resources)\n            shadowApplication.setPluginClassLoader(pluginClassLoader)\n            shadowApplication.setPluginComponentLauncher(componentManager)\n            shadowApplication.setBroadcasts(pluginManifest.receivers)\n            shadowApplication.setAppComponentFactory(appComponentFactory)\n            shadowApplication.applicationInfo = pluginApplicationInfo\n            shadowApplication.setBusinessName(loadParameters.businessName)\n            shadowApplication.setPluginPartKey(partKey)\n            shadowApplication.setShadowApplication(shadowApplication)\n\n            //和ShadowActivityDelegate.initPluginActivity一样，attachBaseContext放到最后\n            shadowApplication.setHostApplicationContextAsBase(hostAppContext)\n            shadowApplication.setTheme(pluginManifest.applicationTheme)\n            return shadowApplication\n        } catch (e: Exception) {\n            throw CreateApplicationException(e)\n        }\n\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreatePluginApplicationInfoBloc.kt",
    "content": "package com.tencent.shadow.core.loader.blocs\n\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.os.Build\nimport com.tencent.shadow.core.common.InstalledApk\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport com.tencent.shadow.core.runtime.ShadowContext\nimport java.io.File\n\nobject CreatePluginApplicationInfoBloc {\n    fun create(\n        installedApk: InstalledApk,\n        loadParameters: LoadParameters,\n        pluginManifest: PluginManifest,\n        hostAppContext: Context\n    ): ApplicationInfo {\n        val result = ApplicationInfo(hostAppContext.applicationInfo)\n        result.sourceDir = installedApk.apkFilePath\n        result.nativeLibraryDir = installedApk.libraryPath\n        result.dataDir = makeDataDir(loadParameters, hostAppContext).absolutePath\n\n        result.packageName = pluginManifest.applicationPackageName\n        result.className = pluginManifest.applicationClassName\n        result.theme = pluginManifest.applicationTheme\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            result.appComponentFactory = pluginManifest.appComponentFactory\n        }\n        return result\n    }\n\n    fun makeDataDir(loadParameters: LoadParameters, hostAppContext: Context): File {\n        val tempContext = ShadowContext(hostAppContext, 0).apply {\n            setBusinessName(loadParameters.businessName)\n        }\n        val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            tempContext.dataDir\n        } else {\n            File(tempContext.filesDir, \"dataDir\")\n        }\n        dataDir.mkdirs()\n        return dataDir\n    }\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/CreateResourceBloc.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.blocs\n\nimport android.annotation.TargetApi\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.content.res.XmlResourceParser\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.util.AttributeSet\nimport android.util.DisplayMetrics\nimport android.util.TypedValue\nimport android.webkit.WebView\nimport java.util.concurrent.CountDownLatch\n\nobject CreateResourceBloc {\n    /**\n     * 构造插件Resources时有两个方案，都要解决一个共同的问题：\n     * 系统有可能从宿主Manifest中获取app icon或者logo的资源ID，\n     * 然后直接向插件的Resources对象查询这些资源。\n     *\n     * 第一个方案是MixResources方案，但该方案依赖Resources的Deprecated构造器，\n     * 未来可能会不可用。实际上Resources的构造器如果不取消的话，这个方案可以一直使用下去。\n     *\n     * 第二个方案是利用资源分区，这是一个和AAB设计中dynamic-feature相同的方案，\n     * 将宿主和插件apk添加到同一个Resources对象中。\n     * 尽管构造这种带有多资源ID分区的Resources对象所需的API在低版本系统上就已经有了，\n     * 但通过不断测试发现MAX_API_FOR_MIX_RESOURCES及更低的API系统上，有个别API不能正确支持非0x7f分区的资源。\n     */\n    const val MAX_API_FOR_MIX_RESOURCES = Build.VERSION_CODES.O_MR1\n\n    fun create(archiveFilePath: String, hostAppContext: Context): Resources {\n        triggerWebViewHookResources(hostAppContext)\n\n        val packageManager = hostAppContext.packageManager\n        val applicationInfo = ApplicationInfo()\n        val hostApplicationInfo = hostAppContext.applicationInfo\n        applicationInfo.packageName = hostApplicationInfo.packageName\n        applicationInfo.uid = hostApplicationInfo.uid\n\n        if (Build.VERSION.SDK_INT > MAX_API_FOR_MIX_RESOURCES) {\n            fillApplicationInfoForNewerApi(applicationInfo, hostApplicationInfo, archiveFilePath)\n        } else {\n            fillApplicationInfoForLowerApi(applicationInfo, hostApplicationInfo, archiveFilePath)\n        }\n\n        try {\n            val pluginResource = packageManager.getResourcesForApplication(applicationInfo)\n\n            return if (Build.VERSION.SDK_INT > MAX_API_FOR_MIX_RESOURCES) {\n                pluginResource\n            } else {\n                val hostResources = hostAppContext.resources\n                MixResources(pluginResource, hostResources)\n            }\n        } catch (e: PackageManager.NameNotFoundException) {\n            throw RuntimeException(e)\n        }\n\n    }\n\n    /**\n     * WebView初始化时会向系统构造的Resources对象注入webview.apk，\n     * 以便WebView可以使用自己的资源。\n     *\n     * 由于它不会向插件构造的Resources对象注入apk，\n     * 所以我们先初始化它，让它注入给宿主，等插件构造Resources时从宿主中复制出该apk路径。\n     */\n    private fun triggerWebViewHookResources(hostAppContext: Context) {\n        //先用宿主context初始化一个WebView，以便WebView的逻辑去修改sharedLibraryFiles，将webview.apk添加进去\n        val latch = CountDownLatch(1)\n        Handler(Looper.getMainLooper()).post {\n            try {\n                WebView(hostAppContext)\n            } catch (ignored: Exception) {\n                // API 26虚拟机报No WebView installed\n            }\n            latch.countDown()\n        }\n        latch.await()\n    }\n\n    private fun fillApplicationInfoForNewerApi(\n        applicationInfo: ApplicationInfo,\n        hostApplicationInfo: ApplicationInfo,\n        pluginApkPath: String\n    ) {\n        /**\n         * 这里虽然sourceDir和sharedLibraryFiles中指定的apk都会进入Resources对象，\n         * 但是只有资源id分区大于0x7f时才能在加载之后保持住资源id分区。\n         * 如果把宿主的apk路径放到sharedLibraryFiles中，我们假设宿主资源id分区是0x7f，\n         * 则加载后会变为一个随机的分区，如0x30。因此放入sharedLibraryFiles中的apk的\n         * 资源id分区都需要改为0x80或更大的值。\n         *\n         * 考虑到现网可能已经有旧方案运行的宿主和插件，而宿主不易更新。\n         * 因此新方案假设宿主保持0x7f固定不能修改，但是插件可以重新编译新版本修改资源id分区。\n         * 因此把插件apk路径放到sharedLibraryFiles中。\n         *\n         * 复制宿主的sharedLibraryFiles，主要是为了获取前面WebView初始化时，\n         * 系统使用私有API注入的webview.apk\n         */\n        applicationInfo.publicSourceDir = hostApplicationInfo.publicSourceDir\n        applicationInfo.sourceDir = hostApplicationInfo.sourceDir\n\n        // hostSharedLibraryFiles中可能有webview通过私有api注入的webview.apk\n        val hostSharedLibraryFiles = hostApplicationInfo.sharedLibraryFiles\n        val otherApksAddToResources =\n            if (hostSharedLibraryFiles == null)\n                arrayOf(pluginApkPath)\n            else\n                arrayOf(\n                    *hostSharedLibraryFiles,\n                    pluginApkPath\n                )\n\n        applicationInfo.sharedLibraryFiles = otherApksAddToResources\n    }\n\n    /**\n     * API 25及以下系统，单独构造插件资源\n     */\n    private fun fillApplicationInfoForLowerApi(\n        applicationInfo: ApplicationInfo,\n        hostApplicationInfo: ApplicationInfo,\n        pluginApkPath: String\n    ) {\n        applicationInfo.publicSourceDir = pluginApkPath\n        applicationInfo.sourceDir = pluginApkPath\n        applicationInfo.sharedLibraryFiles = hostApplicationInfo.sharedLibraryFiles\n    }\n}\n\n/**\n * 在API 25及以下代替设置sharedLibraryFiles后通过getResourcesForApplication创建资源的方案。\n * 因调用addAssetPath方法也无法满足CreateResourceTest涉及的场景。\n */\n@Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\")\n@TargetApi(CreateResourceBloc.MAX_API_FOR_MIX_RESOURCES)\nprivate class MixResources(\n    private val mainResources: Resources,\n    private val sharedResources: Resources\n) : Resources(mainResources.assets, mainResources.displayMetrics, mainResources.configuration) {\n\n    private var beforeInitDone = false\n    private var updateConfigurationCalledInInit = false\n\n    /**\n     * 低版本系统中Resources构造器中会调用updateConfiguration方法，\n     * 此时mainResources还没有初始化。\n     */\n    init {\n        if (updateConfigurationCalledInInit) {\n            updateConfiguration(mainResources.configuration, mainResources.displayMetrics)\n        }\n        beforeInitDone = true\n    }\n\n    private fun <R> tryMainThenShared(function: (res: Resources) -> R) = try {\n        function(mainResources)\n    } catch (e: NotFoundException) {\n        function(sharedResources)\n    }\n\n    override fun getText(id: Int) = tryMainThenShared { it.getText(id) }\n\n    override fun getText(id: Int, def: CharSequence?) = tryMainThenShared { it.getText(id, def) }\n\n    override fun getQuantityText(id: Int, quantity: Int) =\n        tryMainThenShared { it.getQuantityText(id, quantity) }\n\n    override fun getString(id: Int) =\n        tryMainThenShared { it.getString(id) }\n\n    override fun getString(id: Int, vararg formatArgs: Any?) =\n        tryMainThenShared { it.getString(id, *formatArgs) }\n\n\n    override fun getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any?) =\n        tryMainThenShared { it.getQuantityString(id, quantity, *formatArgs) }\n\n    override fun getQuantityString(id: Int, quantity: Int) =\n        tryMainThenShared {\n            it.getQuantityString(id, quantity)\n        }\n\n    override fun getTextArray(id: Int) =\n        tryMainThenShared {\n            it.getTextArray(id)\n        }\n\n    override fun getStringArray(id: Int) =\n        tryMainThenShared {\n            it.getStringArray(id)\n        }\n\n    override fun getIntArray(id: Int) =\n        tryMainThenShared {\n            it.getIntArray(id)\n        }\n\n    override fun obtainTypedArray(id: Int) =\n        tryMainThenShared {\n            it.obtainTypedArray(id)\n        }\n\n    override fun getDimension(id: Int) =\n        tryMainThenShared {\n            it.getDimension(id)\n        }\n\n    override fun getDimensionPixelOffset(id: Int) =\n        tryMainThenShared {\n            it.getDimensionPixelOffset(id)\n        }\n\n    override fun getDimensionPixelSize(id: Int) =\n        tryMainThenShared {\n            it.getDimensionPixelSize(id)\n        }\n\n    override fun getFraction(id: Int, base: Int, pbase: Int) =\n        tryMainThenShared {\n            it.getFraction(id, base, pbase)\n        }\n\n    override fun getDrawable(id: Int) =\n        tryMainThenShared {\n            it.getDrawable(id)\n        }\n\n    override fun getDrawable(id: Int, theme: Theme?) =\n        tryMainThenShared {\n            it.getDrawable(id, theme)\n        }\n\n    override fun getDrawableForDensity(id: Int, density: Int) =\n        tryMainThenShared {\n            it.getDrawableForDensity(id, density)\n        }\n\n    override fun getDrawableForDensity(id: Int, density: Int, theme: Theme?) =\n        tryMainThenShared {\n            it.getDrawableForDensity(id, density, theme)\n        }\n\n    override fun getMovie(id: Int) =\n        tryMainThenShared {\n            it.getMovie(id)\n        }\n\n    override fun getColor(id: Int) =\n        tryMainThenShared {\n            it.getColor(id)\n        }\n\n    override fun getColor(id: Int, theme: Theme?) =\n        tryMainThenShared {\n            it.getColor(id, theme)\n        }\n\n    override fun getColorStateList(id: Int) =\n        tryMainThenShared {\n            it.getColorStateList(id)\n        }\n\n    override fun getColorStateList(id: Int, theme: Theme?) =\n        tryMainThenShared {\n            it.getColorStateList(id, theme)\n        }\n\n    override fun getBoolean(id: Int) =\n        tryMainThenShared {\n            it.getBoolean(id)\n        }\n\n    override fun getInteger(id: Int) =\n        tryMainThenShared {\n            it.getInteger(id)\n        }\n\n    override fun getLayout(id: Int) =\n        tryMainThenShared {\n            it.getLayout(id)\n        }\n\n    override fun getAnimation(id: Int) =\n        tryMainThenShared {\n            it.getAnimation(id)\n        }\n\n    override fun getXml(id: Int) =\n        tryMainThenShared {\n            it.getXml(id)\n        }\n\n    override fun openRawResource(id: Int) =\n        tryMainThenShared {\n            it.openRawResource(id)\n        }\n\n    override fun openRawResource(id: Int, value: TypedValue?) =\n        tryMainThenShared {\n            it.openRawResource(id, value)\n        }\n\n    override fun openRawResourceFd(id: Int) =\n        tryMainThenShared {\n            it.openRawResourceFd(id)\n        }\n\n    override fun getValue(id: Int, outValue: TypedValue?, resolveRefs: Boolean) =\n        tryMainThenShared {\n            it.getValue(id, outValue, resolveRefs)\n        }\n\n    override fun getValue(name: String?, outValue: TypedValue?, resolveRefs: Boolean) =\n        tryMainThenShared {\n            it.getValue(name, outValue, resolveRefs)\n        }\n\n    override fun getValueForDensity(\n        id: Int,\n        density: Int,\n        outValue: TypedValue?,\n        resolveRefs: Boolean\n    ) =\n        tryMainThenShared {\n            it.getValueForDensity(id, density, outValue, resolveRefs)\n        }\n\n    override fun obtainAttributes(set: AttributeSet?, attrs: IntArray?) =\n        tryMainThenShared {\n            it.obtainAttributes(set, attrs)\n        }\n\n    override fun updateConfiguration(config: Configuration?, metrics: DisplayMetrics?) {\n        if (beforeInitDone) {\n            tryMainThenShared {\n                it.updateConfiguration(config, metrics)\n            }\n        }\n    }\n\n    override fun getDisplayMetrics() =\n        tryMainThenShared {\n            it.getDisplayMetrics()\n        }\n\n    override fun getConfiguration() =\n        tryMainThenShared {\n            it.getConfiguration()\n        }\n\n    override fun getIdentifier(name: String?, defType: String?, defPackage: String?) =\n        tryMainThenShared {\n            it.getIdentifier(name, defType, defPackage)\n        }\n\n    override fun getResourceName(resid: Int) =\n        tryMainThenShared {\n            it.getResourceName(resid)\n        }\n\n    override fun getResourcePackageName(resid: Int) =\n        tryMainThenShared {\n            it.getResourcePackageName(resid)\n        }\n\n    override fun getResourceTypeName(resid: Int) =\n        tryMainThenShared {\n            it.getResourceTypeName(resid)\n        }\n\n    override fun getResourceEntryName(resid: Int) =\n        tryMainThenShared {\n            it.getResourceEntryName(resid)\n        }\n\n    override fun parseBundleExtras(parser: XmlResourceParser?, outBundle: Bundle?) =\n        tryMainThenShared {\n            it.parseBundleExtras(parser, outBundle)\n        }\n\n    override fun parseBundleExtra(tagName: String?, attrs: AttributeSet?, outBundle: Bundle?) =\n        tryMainThenShared {\n            it.parseBundleExtra(tagName, attrs, outBundle)\n        }\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadApkBloc.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.blocs\n\nimport com.tencent.shadow.core.common.InstalledApk\nimport com.tencent.shadow.core.common.Logger\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.loader.classloaders.CombineClassLoader\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader\nimport com.tencent.shadow.core.loader.exceptions.LoadApkException\nimport com.tencent.shadow.core.loader.infos.PluginParts\nimport java.io.File\n\n/**\n * 加载插件到ClassLoader中\n *\n * @author cubershi\n */\nobject LoadApkBloc {\n\n    /**\n     * 加载插件到ClassLoader中.\n     *\n     * @param installedPlugin    已安装（PluginManager已经下载解包）的插件\n     * @return 加载了插件的ClassLoader\n     */\n    @Throws(LoadApkException::class)\n    fun loadPlugin(\n        installedApk: InstalledApk,\n        loadParameters: LoadParameters,\n        pluginPartsMap: MutableMap<String, PluginParts>\n    ): PluginClassLoader {\n        val apk = File(installedApk.apkFilePath)\n        val odexDir = if (installedApk.oDexPath == null) null else File(installedApk.oDexPath)\n        val dependsOn = loadParameters.dependsOn\n        //Logger类一定打包在宿主中，所在的classLoader即为加载宿主的classLoader\n        val hostClassLoader: ClassLoader = Logger::class.java.classLoader!!\n        val hostParentClassLoader = hostClassLoader.parent\n        if (dependsOn == null || dependsOn.isEmpty()) {\n            return PluginClassLoader(\n                apk.absolutePath,\n                odexDir,\n                installedApk.libraryPath,\n                hostClassLoader,\n                hostParentClassLoader,\n                loadParameters.hostWhiteList\n            )\n        } else if (dependsOn.size == 1) {\n            val partKey = dependsOn[0]\n            val pluginParts = pluginPartsMap[partKey]\n            if (pluginParts == null) {\n                throw LoadApkException(\"加载\" + loadParameters.partKey + \"时它的依赖\" + partKey + \"还没有加载\")\n            } else {\n                return PluginClassLoader(\n                    apk.absolutePath,\n                    odexDir,\n                    installedApk.libraryPath,\n                    pluginParts.classLoader,\n                    null,\n                    loadParameters.hostWhiteList\n                )\n            }\n        } else {\n            val dependsOnClassLoaders = dependsOn.map {\n                val pluginParts = pluginPartsMap[it]\n                if (pluginParts == null) {\n                    throw LoadApkException(\"加载\" + loadParameters.partKey + \"时它的依赖\" + it + \"还没有加载\")\n                } else {\n                    pluginParts.classLoader\n                }\n            }.toTypedArray()\n            val combineClassLoader =\n                CombineClassLoader(dependsOnClassLoaders, hostParentClassLoader)\n            return PluginClassLoader(\n                apk.absolutePath,\n                odexDir,\n                installedApk.libraryPath,\n                combineClassLoader,\n                null,\n                loadParameters.hostWhiteList\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/blocs/LoadPluginBloc.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.blocs\n\nimport android.content.Context\nimport com.tencent.shadow.core.common.InstalledApk\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.loader.exceptions.LoadPluginException\nimport com.tencent.shadow.core.loader.infos.PluginParts\nimport com.tencent.shadow.core.loader.managers.ComponentManager\nimport com.tencent.shadow.core.loader.managers.PluginPackageManagerImpl\nimport com.tencent.shadow.core.runtime.PluginPartInfo\nimport com.tencent.shadow.core.runtime.PluginPartInfoManager\nimport com.tencent.shadow.core.runtime.ShadowAppComponentFactory\nimport java.io.File\nimport java.util.concurrent.Callable\nimport java.util.concurrent.ExecutorService\nimport java.util.concurrent.Future\nimport java.util.concurrent.locks.ReentrantLock\nimport kotlin.concurrent.withLock\n\nobject LoadPluginBloc {\n    @Throws(LoadPluginException::class)\n    fun loadPlugin(\n        executorService: ExecutorService,\n        componentManager: ComponentManager,\n        lock: ReentrantLock,\n        pluginPartsMap: MutableMap<String, PluginParts>,\n        hostAppContext: Context,\n        installedApk: InstalledApk,\n        loadParameters: LoadParameters\n    ): Future<*> {\n        if (installedApk.apkFilePath == null) {\n            throw LoadPluginException(\"apkFilePath==null\")\n        } else {\n            val buildClassLoader = executorService.submit(Callable {\n                lock.withLock {\n                    LoadApkBloc.loadPlugin(installedApk, loadParameters, pluginPartsMap)\n                }\n            })\n\n            val buildPluginManifest = executorService.submit(Callable {\n                val pluginClassLoader = buildClassLoader.get()\n                val pluginManifest = pluginClassLoader.loadPluginManifest()\n                CheckPackageNameBloc.check(pluginManifest, hostAppContext)\n                pluginManifest\n            })\n\n            val buildPluginApplicationInfo = executorService.submit(Callable {\n                val pluginManifest = buildPluginManifest.get()\n                val pluginApplicationInfo = CreatePluginApplicationInfoBloc.create(\n                    installedApk,\n                    loadParameters,\n                    pluginManifest,\n                    hostAppContext\n                )\n                pluginApplicationInfo\n            })\n\n            val buildPackageManager = executorService.submit(Callable {\n                val pluginApplicationInfo = buildPluginApplicationInfo.get()\n                val hostPackageManager = hostAppContext.packageManager\n                PluginPackageManagerImpl(\n                    pluginApplicationInfo,\n                    installedApk.apkFilePath,\n                    componentManager,\n                    hostPackageManager,\n                )\n            })\n\n            val buildResources = executorService.submit(Callable {\n                CreateResourceBloc.create(installedApk.apkFilePath, hostAppContext)\n            })\n\n            val buildAppComponentFactory = executorService.submit(Callable {\n                val pluginClassLoader = buildClassLoader.get()\n                val pluginManifest = buildPluginManifest.get()\n                val appComponentFactory = pluginManifest.appComponentFactory\n                if (appComponentFactory != null) {\n                    val clazz = pluginClassLoader.loadClass(appComponentFactory)\n                    ShadowAppComponentFactory::class.java.cast(clazz.newInstance())\n                } else ShadowAppComponentFactory()\n            })\n\n            val buildApplication = executorService.submit(Callable {\n                val pluginClassLoader = buildClassLoader.get()\n                val resources = buildResources.get()\n                val appComponentFactory = buildAppComponentFactory.get()\n                val pluginManifest = buildPluginManifest.get()\n                val pluginApplicationInfo = buildPluginApplicationInfo.get()\n\n                CreateApplicationBloc.createShadowApplication(\n                    pluginClassLoader,\n                    loadParameters,\n                    pluginManifest,\n                    resources,\n                    hostAppContext,\n                    componentManager,\n                    pluginApplicationInfo,\n                    appComponentFactory\n                )\n            })\n\n            val buildRunningPlugin = executorService.submit {\n                if (File(installedApk.apkFilePath).exists().not()) {\n                    throw LoadPluginException(\"插件文件不存在.pluginFile==\" + installedApk.apkFilePath)\n                }\n                val pluginPackageManager = buildPackageManager.get()\n                val pluginClassLoader = buildClassLoader.get()\n                val resources = buildResources.get()\n                val shadowApplication = buildApplication.get()\n                val appComponentFactory = buildAppComponentFactory.get()\n                val pluginManifest = buildPluginManifest.get()\n                lock.withLock {\n                    componentManager.addPluginApkInfo(\n                        pluginManifest,\n                        loadParameters,\n                        installedApk.apkFilePath,\n                    )\n                    pluginPartsMap[loadParameters.partKey] = PluginParts(\n                        appComponentFactory,\n                        shadowApplication,\n                        pluginClassLoader,\n                        resources,\n                        pluginPackageManager\n                    )\n                    PluginPartInfoManager.addPluginInfo(\n                        pluginClassLoader, PluginPartInfo(\n                            shadowApplication, resources,\n                            pluginClassLoader, pluginPackageManager\n                        )\n                    )\n                }\n            }\n\n            return buildRunningPlugin\n        }\n    }\n\n\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/CombineClassLoader.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.classloaders\n\nimport android.os.Build\n\nclass CombineClassLoader(private val classLoaders: Array<out ClassLoader>, parent: ClassLoader) :\n    ClassLoader(parent) {\n\n    @Throws(ClassNotFoundException::class)\n    override fun loadClass(name: String, resolve: Boolean): Class<*> {\n        var c: Class<*>? = findLoadedClass(name)\n        val classNotFoundException = ClassNotFoundException(name)\n        if (c == null) {\n            try {\n                c = super.loadClass(name, resolve)\n            } catch (e: ClassNotFoundException) {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                    classNotFoundException.addSuppressed(e)\n                }\n            }\n\n            if (c == null) {\n                for (classLoader in classLoaders) {\n                    try {\n                        c = classLoader.loadClass(name)!!\n                        break\n                    } catch (e: ClassNotFoundException) {\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                            classNotFoundException.addSuppressed(e)\n                        }\n                    }\n                }\n                if (c == null) {\n                    throw classNotFoundException\n                }\n            }\n        }\n        return c\n    }\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoader.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.classloaders\n\nimport android.os.Build\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport dalvik.system.BaseDexClassLoader\nimport org.jetbrains.annotations.TestOnly\nimport java.io.File\n\n\n/**\n * 用于加载插件的ClassLoader,插件内部的classLoader树结构如下\n *                       BootClassLoader\n *                              |\n *                      xxxClassLoader\n *                        |        |\n *               PathClassLoader   |\n *                 |               |\n *     PluginClassLoaderA  CombineClassLoader\n *                                 |\n *  PluginClassLoaderB        PluginClassLoaderC\n *\n */\nclass PluginClassLoader(\n    dexPath: String,\n    optimizedDirectory: File?,\n    librarySearchPath: String?,\n    parent: ClassLoader,\n    private val specialClassLoader: ClassLoader?, hostWhiteList: Array<String>?\n) : BaseDexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent) {\n\n    /**\n     * 宿主的白名单包名\n     * 在白名单包里面的宿主类，插件才可以访问\n     */\n    private val allHostWhiteTrie = PackageNameTrie()\n\n    private val loaderClassLoader = PluginClassLoader::class.java.classLoader!!\n\n    init {\n        hostWhiteList?.forEach {\n            allHostWhiteTrie.insert(it)\n        }\n\n        //org.apache.commons.logging是非常特殊的的包,由系统放到App的PathClassLoader中.\n        allHostWhiteTrie.insert(\"org.apache.commons.logging\")\n\n        //Android 9.0以下的系统里面带有http包，走系统的不走本地的\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            allHostWhiteTrie.insert(\"org.apache.http\")\n            allHostWhiteTrie.insert(\"org.apache.http.**\")\n        }\n    }\n\n    @Throws(ClassNotFoundException::class)\n    override fun loadClass(className: String, resolve: Boolean): Class<*> {\n        var clazz: Class<*>? = findLoadedClass(className)\n\n        if (clazz == null) {\n            //specialClassLoader 为null 表示该classLoader依赖了其他的插件classLoader，需要遵循双亲委派\n            if (specialClassLoader == null) {\n                return super.loadClass(className, resolve)\n            }\n\n            //插件依赖跟loader一起打包的runtime类，如ShadowActivity，从loader的ClassLoader加载\n            if (className.subStringBeforeDot() == \"com.tencent.shadow.core.runtime\") {\n                return loaderClassLoader.loadClass(className)\n            }\n\n            //包名在白名单中的类按双亲委派逻辑，从宿主中加载\n            if (className.inPackage(allHostWhiteTrie)) {\n                return super.loadClass(className, resolve)\n            }\n\n            var suppressed: ClassNotFoundException? = null\n            try {\n                //正常的ClassLoader这里是parent.loadClass,插件用specialClassLoader以跳过parent\n                clazz = specialClassLoader.loadClass(className)!!\n            } catch (e: ClassNotFoundException) {\n                suppressed = e\n            }\n            if (clazz == null) {\n                try {\n                    clazz = findClass(className)!!\n                } catch (e: ClassNotFoundException) {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                        e.addSuppressed(suppressed)\n                    }\n                    throw e\n                }\n\n            }\n        }\n\n        return clazz\n    }\n\n    internal fun loadPluginManifest(): PluginManifest {\n        try {\n            val clazz = findClass(\"com.tencent.shadow.core.manifest_parser.PluginManifest\")\n            return PluginManifest::class.java.cast(clazz.newInstance())\n        } catch (e: ClassNotFoundException) {\n            throw Error(\n                \"请注意每个插件apk构建时都需要\" +\n                        \"apply plugin: 'com.tencent.shadow.plugin'\", e\n            )\n        }\n    }\n\n}\n\nprivate fun String.subStringBeforeDot() = substringBeforeLast('.', \"\")\n\n@Deprecated(\"use PackageNameTrie instead.\")\n@TestOnly\ninternal fun String.inPackage(packageNames: Array<String>): Boolean {\n    val trie = PackageNameTrie()\n    packageNames.forEach {\n        trie.insert(it)\n    }\n    return inPackage(trie)\n}\n\nprivate fun String.inPackage(packageNames: PackageNameTrie): Boolean {\n    val packageName = subStringBeforeDot()\n    return packageNames.isMatch(packageName)\n}\n\n/**\n * 基于Trie算法对包名进行前缀匹配\n */\nprivate class PackageNameTrie {\n    private class Node {\n        val subNodes = mutableMapOf<String, Node>()\n        var isLastPackageOfARule = false\n    }\n\n    private val root = Node()\n\n    fun insert(packageNameRule: String) {\n        var node = root\n        packageNameRule.split('.').forEach {\n            if (it.isEmpty()) return //\"\",\".*\",\".**\"这种无包名情况不允许设置\n\n            var subNode = node.subNodes[it]\n            if (subNode == null) {\n                subNode = Node()\n                node.subNodes[it] = subNode\n            }\n            node = subNode\n        }\n        node.isLastPackageOfARule = true\n    }\n\n    fun isMatch(packageName: String): Boolean {\n        var node = root\n\n        val split = packageName.split('.')\n        val lastIndex = split.size - 1\n        for ((index, name) in split.withIndex()) {\n            // 只要下级包名规则中有**，就完成了匹配\n            val twoStars = node.subNodes[\"**\"]\n            if (twoStars != null) {\n                return true\n            }\n\n            // 剩最后一级包名时，如果规则是*则完成比配\n            if (index == lastIndex) {\n                val oneStar = node.subNodes[\"*\"]\n                if (oneStar != null) {\n                    return true\n                }\n            }\n\n            // 找不到下级包名时即匹配失败\n            val subNode = node.subNodes[name]\n            if (subNode == null) {\n                return false\n            } else {\n                node = subNode\n            }\n        }\n        return node.isLastPackageOfARule\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/DI.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.delegates\n\ninterface DI {\n    fun inject(delegate: ShadowDelegate, partKey: String)\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/PackageManagerWrapper.java",
    "content": "package com.tencent.shadow.core.loader.delegates;\n\nimport android.annotation.SuppressLint;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.ChangedPackages;\nimport android.content.pm.FeatureInfo;\nimport android.content.pm.InstallSourceInfo;\nimport android.content.pm.InstrumentationInfo;\nimport android.content.pm.ModuleInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageInstaller;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PermissionGroupInfo;\nimport android.content.pm.PermissionInfo;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.pm.SharedLibraryInfo;\nimport android.content.pm.VersionedPackage;\nimport android.content.res.Resources;\nimport android.content.res.XmlResourceParser;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Bundle;\nimport android.os.UserHandle;\n\nimport java.util.List;\nimport java.util.Set;\n\n@SuppressLint(\"NewApi\")\nclass PackageManagerWrapper extends PackageManager {\n    final private PackageManager proxy;\n\n    PackageManagerWrapper(PackageManager proxy) {\n        this.proxy = proxy;\n    }\n\n    @Override\n    public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {\n        return proxy.getPackageInfo(packageName, flags);\n    }\n\n    @Override\n    public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) throws NameNotFoundException {\n        return proxy.getPackageInfo(versionedPackage, flags);\n    }\n\n    @Override\n    public String[] currentToCanonicalPackageNames(String[] packageNames) {\n        return proxy.currentToCanonicalPackageNames(packageNames);\n    }\n\n    @Override\n    public String[] canonicalToCurrentPackageNames(String[] packageNames) {\n        return proxy.canonicalToCurrentPackageNames(packageNames);\n    }\n\n    @Override\n    public Intent getLaunchIntentForPackage(String packageName) {\n        return proxy.getLaunchIntentForPackage(packageName);\n    }\n\n    @Override\n    public Intent getLeanbackLaunchIntentForPackage(String packageName) {\n        return proxy.getLeanbackLaunchIntentForPackage(packageName);\n    }\n\n    @Override\n    public int[] getPackageGids(String packageName) throws NameNotFoundException {\n        return proxy.getPackageGids(packageName);\n    }\n\n    @Override\n    public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException {\n        return proxy.getPackageGids(packageName, flags);\n    }\n\n    @Override\n    public int getPackageUid(String packageName, int flags) throws NameNotFoundException {\n        return proxy.getPackageUid(packageName, flags);\n    }\n\n    @Override\n    public PermissionInfo getPermissionInfo(String permName, int flags) throws NameNotFoundException {\n        return proxy.getPermissionInfo(permName, flags);\n    }\n\n    @Override\n    public List<PermissionInfo> queryPermissionsByGroup(String permissionGroup, int flags) throws NameNotFoundException {\n        return proxy.queryPermissionsByGroup(permissionGroup, flags);\n    }\n\n    @Override\n    public PermissionGroupInfo getPermissionGroupInfo(String permName, int flags) throws NameNotFoundException {\n        return proxy.getPermissionGroupInfo(permName, flags);\n    }\n\n    @Override\n    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {\n        return proxy.getAllPermissionGroups(flags);\n    }\n\n    @Override\n    public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {\n        return proxy.getApplicationInfo(packageName, flags);\n    }\n\n    @Override\n    public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException {\n        return proxy.getActivityInfo(component, flags);\n    }\n\n    @Override\n    public ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException {\n        return proxy.getReceiverInfo(component, flags);\n    }\n\n    @Override\n    public ServiceInfo getServiceInfo(ComponentName component, int flags) throws NameNotFoundException {\n        return proxy.getServiceInfo(component, flags);\n    }\n\n    @Override\n    public ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException {\n        return proxy.getProviderInfo(component, flags);\n    }\n\n    @Override\n    public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {\n        return proxy.getModuleInfo(packageName, flags);\n    }\n\n    @Override\n    public List<ModuleInfo> getInstalledModules(int flags) {\n        return proxy.getInstalledModules(flags);\n    }\n\n    @Override\n    public List<PackageInfo> getInstalledPackages(int flags) {\n        return proxy.getInstalledPackages(flags);\n    }\n\n    @Override\n    public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {\n        return proxy.getPackagesHoldingPermissions(permissions, flags);\n    }\n\n    @Override\n    public int checkPermission(String permName, String packageName) {\n        return proxy.checkPermission(permName, packageName);\n    }\n\n    @Override\n    public boolean isPermissionRevokedByPolicy(String permName, String packageName) {\n        return proxy.isPermissionRevokedByPolicy(permName, packageName);\n    }\n\n    @Override\n    public boolean addPermission(PermissionInfo info) {\n        return proxy.addPermission(info);\n    }\n\n    @Override\n    public boolean addPermissionAsync(PermissionInfo info) {\n        return proxy.addPermissionAsync(info);\n    }\n\n    @Override\n    public void removePermission(String permName) {\n        proxy.removePermission(permName);\n    }\n\n    @Override\n    public Set<String> getWhitelistedRestrictedPermissions(String packageName, int whitelistFlag) {\n        return proxy.getWhitelistedRestrictedPermissions(packageName, whitelistFlag);\n    }\n\n    @Override\n    public boolean addWhitelistedRestrictedPermission(String packageName, String permName, int whitelistFlags) {\n        return proxy.addWhitelistedRestrictedPermission(packageName, permName, whitelistFlags);\n    }\n\n    @Override\n    public boolean removeWhitelistedRestrictedPermission(String packageName, String permName, int whitelistFlags) {\n        return proxy.removeWhitelistedRestrictedPermission(packageName, permName, whitelistFlags);\n    }\n\n    @Override\n    public boolean setAutoRevokeWhitelisted(String packageName, boolean whitelisted) {\n        return proxy.setAutoRevokeWhitelisted(packageName, whitelisted);\n    }\n\n    @Override\n    public boolean isAutoRevokeWhitelisted(String packageName) {\n        return proxy.isAutoRevokeWhitelisted(packageName);\n    }\n\n    @Override\n    public CharSequence getBackgroundPermissionOptionLabel() {\n        return proxy.getBackgroundPermissionOptionLabel();\n    }\n\n    @Override\n    public int checkSignatures(String packageName1, String packageName2) {\n        return proxy.checkSignatures(packageName1, packageName2);\n    }\n\n    @Override\n    public int checkSignatures(int uid1, int uid2) {\n        return proxy.checkSignatures(uid1, uid2);\n    }\n\n    @Override\n    public String[] getPackagesForUid(int uid) {\n        return proxy.getPackagesForUid(uid);\n    }\n\n    @Override\n    public String getNameForUid(int uid) {\n        return proxy.getNameForUid(uid);\n    }\n\n    @Override\n    public List<ApplicationInfo> getInstalledApplications(int flags) {\n        return proxy.getInstalledApplications(flags);\n    }\n\n    @Override\n    public boolean isInstantApp() {\n        return proxy.isInstantApp();\n    }\n\n    @Override\n    public boolean isInstantApp(String packageName) {\n        return proxy.isInstantApp(packageName);\n    }\n\n    @Override\n    public int getInstantAppCookieMaxBytes() {\n        return proxy.getInstantAppCookieMaxBytes();\n    }\n\n    @Override\n    public byte[] getInstantAppCookie() {\n        return proxy.getInstantAppCookie();\n    }\n\n    @Override\n    public void clearInstantAppCookie() {\n        proxy.clearInstantAppCookie();\n    }\n\n    @Override\n    public void updateInstantAppCookie(byte[] cookie) {\n        proxy.updateInstantAppCookie(cookie);\n    }\n\n    @Override\n    public String[] getSystemSharedLibraryNames() {\n        return proxy.getSystemSharedLibraryNames();\n    }\n\n    @Override\n    public List<SharedLibraryInfo> getSharedLibraries(int flags) {\n        return proxy.getSharedLibraries(flags);\n    }\n\n    @Override\n    public ChangedPackages getChangedPackages(int sequenceNumber) {\n        return proxy.getChangedPackages(sequenceNumber);\n    }\n\n    @Override\n    public FeatureInfo[] getSystemAvailableFeatures() {\n        return proxy.getSystemAvailableFeatures();\n    }\n\n    @Override\n    public boolean hasSystemFeature(String featureName) {\n        return proxy.hasSystemFeature(featureName);\n    }\n\n    @Override\n    public boolean hasSystemFeature(String featureName, int version) {\n        return proxy.hasSystemFeature(featureName, version);\n    }\n\n    @Override\n    public ResolveInfo resolveActivity(Intent intent, int flags) {\n        return proxy.resolveActivity(intent, flags);\n    }\n\n    @Override\n    public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {\n        return proxy.queryIntentActivities(intent, flags);\n    }\n\n    @Override\n    public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) {\n        return proxy.queryIntentActivityOptions(caller, specifics, intent, flags);\n    }\n\n    @Override\n    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {\n        return proxy.queryBroadcastReceivers(intent, flags);\n    }\n\n    @Override\n    public ResolveInfo resolveService(Intent intent, int flags) {\n        return proxy.resolveService(intent, flags);\n    }\n\n    @Override\n    public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {\n        return proxy.queryIntentServices(intent, flags);\n    }\n\n    @Override\n    public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {\n        return proxy.queryIntentContentProviders(intent, flags);\n    }\n\n    @Override\n    public ProviderInfo resolveContentProvider(String authority, int flags) {\n        return proxy.resolveContentProvider(authority, flags);\n    }\n\n    @Override\n    public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {\n        return proxy.queryContentProviders(processName, uid, flags);\n    }\n\n    @Override\n    public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) throws NameNotFoundException {\n        return proxy.getInstrumentationInfo(className, flags);\n    }\n\n    @Override\n    public List<InstrumentationInfo> queryInstrumentation(String targetPackage, int flags) {\n        return proxy.queryInstrumentation(targetPackage, flags);\n    }\n\n    @Override\n    public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {\n        return proxy.getDrawable(packageName, resid, appInfo);\n    }\n\n    @Override\n    public Drawable getActivityIcon(ComponentName activityName) throws NameNotFoundException {\n        return proxy.getActivityIcon(activityName);\n    }\n\n    @Override\n    public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {\n        return proxy.getActivityIcon(intent);\n    }\n\n    @Override\n    public Drawable getActivityBanner(ComponentName activityName) throws NameNotFoundException {\n        return proxy.getActivityBanner(activityName);\n    }\n\n    @Override\n    public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {\n        return proxy.getActivityBanner(intent);\n    }\n\n    @Override\n    public Drawable getDefaultActivityIcon() {\n        return proxy.getDefaultActivityIcon();\n    }\n\n    @Override\n    public Drawable getApplicationIcon(ApplicationInfo info) {\n        return proxy.getApplicationIcon(info);\n    }\n\n    @Override\n    public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {\n        return proxy.getApplicationIcon(packageName);\n    }\n\n    @Override\n    public Drawable getApplicationBanner(ApplicationInfo info) {\n        return proxy.getApplicationBanner(info);\n    }\n\n    @Override\n    public Drawable getApplicationBanner(String packageName) throws NameNotFoundException {\n        return proxy.getApplicationBanner(packageName);\n    }\n\n    @Override\n    public Drawable getActivityLogo(ComponentName activityName) throws NameNotFoundException {\n        return proxy.getActivityLogo(activityName);\n    }\n\n    @Override\n    public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {\n        return proxy.getActivityLogo(intent);\n    }\n\n    @Override\n    public Drawable getApplicationLogo(ApplicationInfo info) {\n        return proxy.getApplicationLogo(info);\n    }\n\n    @Override\n    public Drawable getApplicationLogo(String packageName) throws NameNotFoundException {\n        return proxy.getApplicationLogo(packageName);\n    }\n\n    @Override\n    public Drawable getUserBadgedIcon(Drawable drawable, UserHandle user) {\n        return proxy.getUserBadgedIcon(drawable, user);\n    }\n\n    @Override\n    public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) {\n        return proxy.getUserBadgedDrawableForDensity(drawable, user, badgeLocation, badgeDensity);\n    }\n\n    @Override\n    public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {\n        return proxy.getUserBadgedLabel(label, user);\n    }\n\n    @Override\n    public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {\n        return proxy.getText(packageName, resid, appInfo);\n    }\n\n    @Override\n    public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {\n        return proxy.getXml(packageName, resid, appInfo);\n    }\n\n    @Override\n    public CharSequence getApplicationLabel(ApplicationInfo info) {\n        return proxy.getApplicationLabel(info);\n    }\n\n    @Override\n    public Resources getResourcesForActivity(ComponentName activityName) throws NameNotFoundException {\n        return proxy.getResourcesForActivity(activityName);\n    }\n\n    @Override\n    public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException {\n        return proxy.getResourcesForApplication(app);\n    }\n\n    @Override\n    public Resources getResourcesForApplication(String packageName) throws NameNotFoundException {\n        return proxy.getResourcesForApplication(packageName);\n    }\n\n    @Override\n    public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {\n        return proxy.getPackageArchiveInfo(archiveFilePath, flags);\n    }\n\n    @Override\n    public void verifyPendingInstall(int id, int verificationCode) {\n        proxy.verifyPendingInstall(id, verificationCode);\n    }\n\n    @Override\n    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) {\n        proxy.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay);\n    }\n\n    @Override\n    public void setInstallerPackageName(String targetPackage, String installerPackageName) {\n        proxy.setInstallerPackageName(targetPackage, installerPackageName);\n    }\n\n    @Override\n    @Deprecated\n    public String getInstallerPackageName(String packageName) {\n        return proxy.getInstallerPackageName(packageName);\n    }\n\n    @Override\n    public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException {\n        return proxy.getInstallSourceInfo(packageName);\n    }\n\n    @Override\n    @Deprecated\n    public void addPackageToPreferred(String packageName) {\n        proxy.addPackageToPreferred(packageName);\n    }\n\n    @Override\n    @Deprecated\n    public void removePackageFromPreferred(String packageName) {\n        proxy.removePackageFromPreferred(packageName);\n    }\n\n    @Override\n    @Deprecated\n    public List<PackageInfo> getPreferredPackages(int flags) {\n        return proxy.getPreferredPackages(flags);\n    }\n\n    @Override\n    @Deprecated\n    public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) {\n        proxy.addPreferredActivity(filter, match, set, activity);\n    }\n\n    @Override\n    @Deprecated\n    public void clearPackagePreferredActivities(String packageName) {\n        proxy.clearPackagePreferredActivities(packageName);\n    }\n\n    @Override\n    @Deprecated\n    public int getPreferredActivities(List<IntentFilter> outFilters, List<ComponentName> outActivities, String packageName) {\n        return proxy.getPreferredActivities(outFilters, outActivities, packageName);\n    }\n\n    @Override\n    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {\n        proxy.setComponentEnabledSetting(componentName, newState, flags);\n    }\n\n    @Override\n    public int getComponentEnabledSetting(ComponentName componentName) {\n        return proxy.getComponentEnabledSetting(componentName);\n    }\n\n    @Override\n    public boolean getSyntheticAppDetailsActivityEnabled(String packageName) {\n        return proxy.getSyntheticAppDetailsActivityEnabled(packageName);\n    }\n\n    @Override\n    public void setApplicationEnabledSetting(String packageName, int newState, int flags) {\n        proxy.setApplicationEnabledSetting(packageName, newState, flags);\n    }\n\n    @Override\n    public int getApplicationEnabledSetting(String packageName) {\n        return proxy.getApplicationEnabledSetting(packageName);\n    }\n\n    @Override\n    public boolean isSafeMode() {\n        return proxy.isSafeMode();\n    }\n\n    @Override\n    public boolean isPackageSuspended(String packageName) throws NameNotFoundException {\n        return proxy.isPackageSuspended(packageName);\n    }\n\n    @Override\n    public boolean isPackageSuspended() {\n        return proxy.isPackageSuspended();\n    }\n\n    @Override\n    public Bundle getSuspendedPackageAppExtras() {\n        return proxy.getSuspendedPackageAppExtras();\n    }\n\n    @Override\n    public void setApplicationCategoryHint(String packageName, int categoryHint) {\n        proxy.setApplicationCategoryHint(packageName, categoryHint);\n    }\n\n    @Override\n    public boolean isDeviceUpgrading() {\n        return proxy.isDeviceUpgrading();\n    }\n\n    @Override\n    public PackageInstaller getPackageInstaller() {\n        return proxy.getPackageInstaller();\n    }\n\n    @Override\n    public boolean canRequestPackageInstalls() {\n        return proxy.canRequestPackageInstalls();\n    }\n\n    @Override\n    public boolean hasSigningCertificate(String packageName, byte[] certificate, int type) {\n        return proxy.hasSigningCertificate(packageName, certificate, type);\n    }\n\n    @Override\n    public boolean hasSigningCertificate(int uid, byte[] certificate, int type) {\n        return proxy.hasSigningCertificate(uid, certificate, type);\n    }\n\n    @Override\n    public boolean isAutoRevokeWhitelisted() {\n        return proxy.isAutoRevokeWhitelisted();\n    }\n\n    @Override\n    public boolean isDefaultApplicationIcon(Drawable drawable) {\n        return proxy.isDefaultApplicationIcon(drawable);\n    }\n\n    @Override\n    public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) {\n        proxy.setMimeGroup(mimeGroup, mimeTypes);\n    }\n\n    @Override\n    public Set<String> getMimeGroup(String mimeGroup) {\n        return proxy.getMimeGroup(mimeGroup);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowActivityDelegate.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.delegates\n\nimport android.app.Activity\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ActivityInfo\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.WindowManager\nimport com.tencent.shadow.coding.java_build_config.BuildConfig\nimport com.tencent.shadow.core.common.LoggerFactory\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_ACTIVITY_INFO_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_BUSINESS_NAME_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_CALLING_ACTIVITY_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_CLASS_NAME_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_EXTRAS_BUNDLE_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_LOADER_BUNDLE_KEY\nimport com.tencent.shadow.core.loader.managers.ComponentManager.Companion.CM_PART_KEY\nimport com.tencent.shadow.core.runtime.PluginActivity\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport com.tencent.shadow.core.runtime.ShadowActivity\nimport com.tencent.shadow.core.runtime.container.HostActivityDelegate\nimport com.tencent.shadow.core.runtime.container.HostActivityDelegator\n\n/**\n * 壳子Activity与插件Activity转调关系的实现类\n * 它是抽象的是因为它缺少必要的业务信息.业务必须继承这个类提供业务信息.\n *\n * @author cubershi\n */\nopen class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivityDelegate(),\n    HostActivityDelegate {\n    companion object {\n        const val PLUGIN_OUT_STATE_KEY = \"PLUGIN_OUT_STATE_KEY\"\n        val mLogger = LoggerFactory.getLogger(ShadowActivityDelegate::class.java)\n    }\n\n    protected lateinit var mHostActivityDelegator: HostActivityDelegator\n    private val mPluginActivity get() = super.pluginActivity\n    private lateinit var mBusinessName: String\n    private lateinit var mPartKey: String\n    private lateinit var mBundleForPluginLoader: Bundle\n    private var mRawIntentExtraBundle: Bundle? = null\n    private var mPluginActivityCreated = false\n    private var mDependenciesInjected = false\n    private var mRecreateCalled = false\n\n    /**\n     * 判断是否调用过OnWindowAttributesChanged，如果调用过就说明需要在onCreate之前调用\n     */\n    private var mCallOnWindowAttributesChanged = false\n    private var mBeforeOnCreateOnWindowAttributesChangedCalledParams: WindowManager.LayoutParams? =\n        null\n\n    override fun setDelegator(hostActivityDelegator: HostActivityDelegator) {\n        mHostActivityDelegator = hostActivityDelegator\n    }\n\n    override fun getPluginActivity(): Any = mPluginActivity\n\n    private lateinit var mCurrentConfiguration: Configuration\n    private var mPluginHandleConfigurationChange: Int = 0\n    private var mCallingActivity: ComponentName? = null\n    protected lateinit var mPluginActivityInfo: PluginManifest.ActivityInfo\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        val pluginInitBundle = savedInstanceState ?: mHostActivityDelegator.intent.extras!!\n\n        mCallingActivity = pluginInitBundle.getParcelable(CM_CALLING_ACTIVITY_KEY)\n        mBusinessName = pluginInitBundle.getString(CM_BUSINESS_NAME_KEY, \"\")\n        val partKey = pluginInitBundle.getString(CM_PART_KEY)!!\n        mPartKey = partKey\n        mDI.inject(this, partKey)\n        mDependenciesInjected = true\n\n        val bundleForPluginLoader = pluginInitBundle.getBundle(CM_LOADER_BUNDLE_KEY)!!\n        mBundleForPluginLoader = bundleForPluginLoader\n        bundleForPluginLoader.classLoader = this.javaClass.classLoader\n        val pluginActivityClassName = bundleForPluginLoader.getString(CM_CLASS_NAME_KEY)!!\n        val pluginActivityInfo: PluginManifest.ActivityInfo =\n            bundleForPluginLoader.getParcelable(CM_ACTIVITY_INFO_KEY)!!\n        mPluginActivityInfo = pluginActivityInfo\n\n        mCurrentConfiguration = Configuration(resources.configuration)\n        mPluginHandleConfigurationChange =\n            (pluginActivityInfo.configChanges\n                    or ActivityInfo.CONFIG_SCREEN_SIZE//系统本身就会单独对待这个属性，不声明也不会重启Activity。\n                    or ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE//系统本身就会单独对待这个属性，不声明也不会重启Activity。\n                    or 0x20000000 //见ActivityInfo.CONFIG_WINDOW_CONFIGURATION 系统处理属性\n                    )\n        if (savedInstanceState == null) {\n            mRawIntentExtraBundle = pluginInitBundle.getBundle(CM_EXTRAS_BUNDLE_KEY)\n            mHostActivityDelegator.intent.replaceExtras(mRawIntentExtraBundle)\n        }\n        mHostActivityDelegator.intent.setExtrasClassLoader(mPluginClassLoader)\n\n        try {\n            val pluginActivity = mAppComponentFactory.instantiateActivity(\n                mPluginClassLoader,\n                pluginActivityClassName,\n                mHostActivityDelegator.intent\n            )\n            initPluginActivity(pluginActivity, pluginActivityInfo)\n            super.pluginActivity = pluginActivity\n\n            if (mLogger.isDebugEnabled) {\n                mLogger.debug(\n                    \"{} mPluginHandleConfigurationChange=={}\",\n                    mPluginActivity.javaClass.canonicalName,\n                    mPluginHandleConfigurationChange\n                )\n            }\n\n            //使PluginActivity替代ContainerActivity接收Window的Callback\n            mHostActivityDelegator.window.callback = pluginActivity\n\n            //设置插件AndroidManifest.xml 中注册的WindowSoftInputMode\n            mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.softInputMode)\n\n            //Activity.onCreate调用之前应该先收到onWindowAttributesChanged。\n            if (mCallOnWindowAttributesChanged) {\n                pluginActivity.onWindowAttributesChanged(\n                    mBeforeOnCreateOnWindowAttributesChangedCalledParams\n                )\n                mBeforeOnCreateOnWindowAttributesChangedCalledParams = null\n            }\n\n            val pluginSavedInstanceState: Bundle? =\n                savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)\n            pluginSavedInstanceState?.classLoader = mPluginClassLoader\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                notifyPluginActivityPreCreated(pluginActivity, pluginSavedInstanceState)\n            }\n            pluginActivity.onCreate(pluginSavedInstanceState)\n            mPluginActivityCreated = true\n        } catch (e: Exception) {\n            throw RuntimeException(e)\n        }\n    }\n\n    private fun initPluginActivity(\n        pluginActivity: PluginActivity,\n        pluginActivityInfo: PluginManifest.ActivityInfo\n    ) {\n        pluginActivity.setHostActivityDelegator(mHostActivityDelegator)\n        pluginActivity.setPluginResources(mPluginResources)\n        pluginActivity.setPluginClassLoader(mPluginClassLoader)\n        pluginActivity.setPluginComponentLauncher(mComponentManager)\n        pluginActivity.setPluginApplication(mPluginApplication)\n        pluginActivity.setShadowApplication(mPluginApplication)\n        pluginActivity.applicationInfo = mPluginApplication.applicationInfo\n        pluginActivity.setBusinessName(mBusinessName)\n        pluginActivity.callingActivity = mCallingActivity\n        pluginActivity.setPluginPartKey(mPartKey)\n\n        //前面的所有set方法都是PluginActivity定义的方法，\n        //业务的Activity子类不会覆盖这些方法。调用它们不会执行业务Activity的任何逻辑。\n        //最后这个setHostContextAsBase会调用插件Activity的attachBaseContext方法，\n        //有可能会执行业务Activity覆盖的逻辑。\n        //所以，这个调用要放在最后。\n        pluginActivity.setHostContextAsBase(mHostActivityDelegator.hostActivity as Context)\n\n        val activityTheme = if (pluginActivityInfo.theme != 0) {\n            pluginActivityInfo.theme\n        } else {\n            pluginActivity.applicationInfo.theme\n        }\n        pluginActivity.setTheme(activityTheme)\n        val screenOrientation = pluginActivityInfo.screenOrientation\n        if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED && screenOrientation != pluginActivity.requestedOrientation) {\n            pluginActivity.requestedOrientation = screenOrientation\n        }\n    }\n\n    override fun getLoaderVersion() = BuildConfig.VERSION_NAME\n\n    override fun onNewIntent(intent: Intent) {\n        val pluginExtras: Bundle? = intent.getBundleExtra(CM_EXTRAS_BUNDLE_KEY)\n        intent.replaceExtras(pluginExtras)\n        mPluginActivity.onNewIntent(intent)\n    }\n\n    override fun onNavigateUpFromChild(arg0: Activity?): Boolean {\n        TODO(\"not implemented\") //To change body of created functions use File | Settings | File Templates.\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        val pluginOutState = Bundle(mPluginClassLoader)\n        mPluginActivity.onSaveInstanceState(pluginOutState)\n        outState.putBundle(PLUGIN_OUT_STATE_KEY, pluginOutState)\n        outState.putString(CM_PART_KEY, mPartKey)\n        outState.putBundle(CM_LOADER_BUNDLE_KEY, mBundleForPluginLoader)\n        if (mRecreateCalled) {\n            outState.putBundle(CM_EXTRAS_BUNDLE_KEY, mHostActivityDelegator.intent.extras)\n        } else {\n            outState.putBundle(CM_EXTRAS_BUNDLE_KEY, mRawIntentExtraBundle)\n        }\n    }\n\n    override fun onChildTitleChanged(arg0: Activity?, arg1: CharSequence?) {\n        TODO(\"not implemented\") //To change body of created functions use File | Settings | File Templates.\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        val diff = newConfig.diff(mCurrentConfiguration)\n        if (mLogger.isDebugEnabled) {\n            mLogger.debug(\n                \"{} onConfigurationChanged diff=={}\",\n                mPluginActivity.javaClass.canonicalName,\n                diff\n            )\n        }\n        if (diff == (diff and mPluginHandleConfigurationChange)) {\n            mPluginActivity.onConfigurationChanged(newConfig)\n            mCurrentConfiguration = Configuration(newConfig)\n        } else {\n            mHostActivityDelegator.superOnConfigurationChanged(newConfig)\n            mHostActivityDelegator.recreate()\n        }\n    }\n\n    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {\n        val pluginSavedInstanceState: Bundle? = savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)\n        mPluginActivity.onRestoreInstanceState(pluginSavedInstanceState)\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        val pluginSavedInstanceState: Bundle? = savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)\n        mPluginActivity.onPostCreate(pluginSavedInstanceState)\n    }\n\n    override fun onWindowAttributesChanged(params: WindowManager.LayoutParams) {\n        if (mPluginActivityCreated) {\n            mPluginActivity.onWindowAttributesChanged(params)\n        } else {\n            mBeforeOnCreateOnWindowAttributesChangedCalledParams = params\n        }\n        mCallOnWindowAttributesChanged = true\n    }\n\n    override fun onApplyThemeResource(theme: Resources.Theme, resid: Int, first: Boolean) {\n        mHostActivityDelegator.superOnApplyThemeResource(theme, resid, first)\n        if (mPluginActivityCreated) {\n            mPluginActivity.onApplyThemeResource(theme, resid, first)\n        }\n    }\n\n    override fun getClassLoader(): ClassLoader {\n        return mPluginClassLoader\n    }\n\n    override fun getLayoutInflater(): LayoutInflater = LayoutInflater.from(mPluginActivity)\n\n    override fun getResources(): Resources {\n        if (mDependenciesInjected) {\n            return mPluginResources;\n        } else {\n            //预期只有android.view.Window.getDefaultFeatures会调用到这个分支，此时我们还无法确定插件资源\n            //而getDefaultFeatures只需要访问系统资源\n            return Resources.getSystem()\n        }\n    }\n\n    override fun recreate() {\n        mRecreateCalled = true\n        mHostActivityDelegator.superRecreate()\n    }\n\n    private fun notifyPluginActivityPreCreated(\n        pluginActivity: ShadowActivity,\n        pluginSavedInstanceState: Bundle?\n    ) {\n        mPluginApplication.mActivityLifecycleCallbacksHolder.notifyPluginActivityPreCreated(\n            pluginActivity,\n            pluginSavedInstanceState\n        )\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowContentProviderDelegate.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.delegates\n\nimport android.annotation.TargetApi\nimport android.content.ContentValues\nimport android.content.res.Configuration\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.CancellationSignal\nimport android.os.ParcelFileDescriptor\n\nimport com.tencent.shadow.core.loader.managers.PluginContentProviderManager\nimport com.tencent.shadow.core.runtime.container.HostContentProviderDelegate\n\nclass ShadowContentProviderDelegate(private val mProviderManager: PluginContentProviderManager) :\n    ShadowDelegate(), HostContentProviderDelegate {\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        mProviderManager.getAllContentProvider().forEach {\n            it.onConfigurationChanged(newConfig)\n        }\n    }\n\n    override fun onLowMemory() {\n        mProviderManager.getAllContentProvider().forEach {\n            it.onLowMemory()\n        }\n    }\n\n    override fun onTrimMemory(level: Int) {\n        mProviderManager.getAllContentProvider().forEach {\n            it.onTrimMemory(level)\n        }\n    }\n\n    override fun onCreate(): Boolean {\n        return true\n    }\n\n\n    override fun query(\n        uri: Uri,\n        projection: Array<String>?,\n        selection: String?,\n        selectionArgs: Array<String>?,\n        sortOrder: String?\n    ): Cursor? {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .query(pluginUri, projection, selection, selectionArgs, sortOrder)\n    }\n\n    override fun getType(uri: Uri): String? {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!.getType(pluginUri)\n    }\n\n    override fun insert(uri: Uri, values: ContentValues): Uri? {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .insert(pluginUri, values)\n    }\n\n    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .delete(pluginUri, selection, selectionArgs)\n    }\n\n    override fun update(\n        uri: Uri,\n        values: ContentValues?,\n        selection: String?,\n        selectionArgs: Array<String>?\n    ): Int {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .update(pluginUri, values, selection, selectionArgs)\n    }\n\n    override fun bulkInsert(uri: Uri, values: Array<out ContentValues>): Int {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .bulkInsert(pluginUri, values)\n    }\n\n    override fun call(method: String, arg: String?, extras: Bundle): Bundle? {\n        val pluginUri = mProviderManager.convert2PluginUri(extras)\n        return mProviderManager.getPluginContentProvider(pluginAuthority = pluginUri.authority!!)!!\n            .call(method, arg, extras)\n    }\n\n    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .openFile(pluginUri, mode)\n    }\n\n    @TargetApi(Build.VERSION_CODES.KITKAT)\n    override fun openFile(\n        uri: Uri,\n        mode: String,\n        signal: CancellationSignal?\n    ): ParcelFileDescriptor? {\n        val pluginUri = mProviderManager.convert2PluginUri(uri)\n        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!\n            .openFile(pluginUri, mode, signal)\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowDelegate.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.delegates\n\nimport android.content.res.Resources\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader\nimport com.tencent.shadow.core.loader.managers.ComponentManager\nimport com.tencent.shadow.core.runtime.ShadowAppComponentFactory\nimport com.tencent.shadow.core.runtime.ShadowApplication\n\nabstract class ShadowDelegate() {\n\n    fun inject(shadowApplication: ShadowApplication) {\n        _pluginApplication = shadowApplication\n    }\n\n    fun inject(appComponentFactory: ShadowAppComponentFactory) {\n        _appComponentFactory = appComponentFactory\n    }\n\n    fun inject(pluginClassLoader: PluginClassLoader) {\n        _pluginClassLoader = pluginClassLoader\n    }\n\n    fun inject(resources: Resources) {\n        _pluginResources = resources\n    }\n\n    fun inject(componentManager: ComponentManager) {\n        _componentManager = componentManager\n    }\n\n    private lateinit var _appComponentFactory: ShadowAppComponentFactory\n    private lateinit var _pluginApplication: ShadowApplication\n    private lateinit var _pluginClassLoader: PluginClassLoader\n    private lateinit var _pluginResources: Resources\n    private lateinit var _componentManager: ComponentManager\n\n    protected val mAppComponentFactory: ShadowAppComponentFactory\n        get() = _appComponentFactory\n    protected val mPluginApplication: ShadowApplication\n        get() = _pluginApplication\n    protected val mPluginClassLoader: PluginClassLoader\n        get() = _pluginClassLoader\n    protected val mPluginResources: Resources\n        get() = _pluginResources\n    protected val mComponentManager: ComponentManager\n        get() = _componentManager\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/delegates/ShadowNativeActivityDelegate.kt",
    "content": "package com.tencent.shadow.core.loader.delegates\n\nimport android.content.ComponentName\nimport android.content.pm.ActivityInfo\nimport android.content.pm.PackageManager\nimport android.view.InputQueue\nimport android.view.SurfaceHolder\nimport com.tencent.shadow.core.runtime.PackageManagerInvokeRedirect\nimport com.tencent.shadow.core.runtime.ShadowNativeActivity\nimport com.tencent.shadow.core.runtime.container.HostNativeActivityDelegate\n\nclass ShadowNativeActivityDelegate(mDI: DI) : ShadowActivityDelegate(mDI),\n    HostNativeActivityDelegate {\n\n    private val mPluginActivity: ShadowNativeActivity\n        get()\n        = super.pluginActivity as ShadowNativeActivity\n\n    override fun surfaceCreated(holder: SurfaceHolder) {\n        return mPluginActivity.surfaceCreated(holder)\n    }\n\n    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {\n        return mPluginActivity.surfaceChanged(holder, format, width, height)\n    }\n\n    override fun surfaceRedrawNeeded(holder: SurfaceHolder) {\n        return mPluginActivity.surfaceRedrawNeeded(holder)\n    }\n\n    override fun surfaceDestroyed(holder: SurfaceHolder) {\n        return mPluginActivity.surfaceDestroyed(holder)\n    }\n\n    override fun onInputQueueCreated(queue: InputQueue) {\n        return mPluginActivity.onInputQueueCreated(queue)\n    }\n\n    override fun onInputQueueDestroyed(queue: InputQueue) {\n        return mPluginActivity.onInputQueueCreated(queue)\n    }\n\n    override fun onGlobalLayout() {\n        return mPluginActivity.onGlobalLayout()\n    }\n\n    //预期只有NativeActivity会调用这个方法\n    override fun getPackageManager(): PackageManager {\n        val pluginPackageManager =\n            PackageManagerInvokeRedirect.getPluginPackageManager(mPluginActivity.classLoader)\n        return object : PackageManagerWrapper(mHostActivityDelegator.superGetPackageManager()) {\n            override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo {\n                return pluginPackageManager.getActivityInfo(component, flags)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/exceptions/CreateApplicationException.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.exceptions\n\nclass CreateApplicationException(cause: Throwable) : Exception(cause)\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/exceptions/LoadApkException.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.exceptions\n\n\nclass LoadApkException(message: String) : Exception(message)\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/exceptions/LoadPluginException.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.exceptions\n\nimport android.annotation.TargetApi\n\nclass LoadPluginException : Exception {\n    constructor() : super()\n    constructor(message: String?) : super(message)\n    constructor(message: String?, cause: Throwable?) : super(message, cause)\n    constructor(cause: Throwable?) : super(cause)\n\n    @TargetApi(24)\n    constructor(\n        message: String?,\n        cause: Throwable?,\n        enableSuppression: Boolean,\n        writableStackTrace: Boolean\n    ) : super(message, cause, enableSuppression, writableStackTrace)\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/exceptions/ParsePluginApkException.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.exceptions\n\n/**\n * 解析插件apk异常\n *\n * @author cubershi\n */\nclass ParsePluginApkException(message: String) : Exception(message)\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/ContainerProviderInfo.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.infos\n\nopen class ContainerProviderInfo(var className: String, var authority: String)\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/infos/PluginParts.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.infos\n\nimport android.content.res.Resources\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader\nimport com.tencent.shadow.core.runtime.PluginPackageManager\nimport com.tencent.shadow.core.runtime.ShadowAppComponentFactory\nimport com.tencent.shadow.core.runtime.ShadowApplication\n\nclass PluginParts(\n    val appComponentFactory: ShadowAppComponentFactory,\n    val application: ShadowApplication,\n    val classLoader: PluginClassLoader,\n    val resources: Resources,\n    val pluginPackageManager: PluginPackageManager\n)"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/ComponentManager.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.managers\n\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.os.Bundle\nimport android.util.Pair\nimport com.tencent.shadow.coding.java_build_config.BuildConfig\nimport com.tencent.shadow.core.load_parameters.LoadParameters\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport com.tencent.shadow.core.runtime.ShadowContext\nimport com.tencent.shadow.core.runtime.ShadowContext.PluginComponentLauncher\nimport com.tencent.shadow.core.runtime.container.DelegateProvider.LOADER_VERSION_KEY\nimport com.tencent.shadow.core.runtime.container.DelegateProvider.PROCESS_ID_KEY\nimport com.tencent.shadow.core.runtime.container.DelegateProviderHolder\nimport com.tencent.shadow.core.runtime.container.GeneratedHostActivityDelegator\n\n/**\n * 插件组件管理\n * 主要功能是管理组件和宿主中注册的壳子之间的配对关系\n *\n * @author cubershi\n */\nabstract class ComponentManager : PluginComponentLauncher {\n    companion object {\n        const val CM_LOADER_BUNDLE_KEY = \"CM_LOADER_BUNDLE\"\n        const val CM_EXTRAS_BUNDLE_KEY = \"CM_EXTRAS_BUNDLE\"\n        const val CM_ACTIVITY_INFO_KEY = \"CM_ACTIVITY_INFO\"\n        const val CM_CLASS_NAME_KEY = \"CM_CLASS_NAME\"\n        const val CM_CALLING_ACTIVITY_KEY = \"CM_CALLING_ACTIVITY_KEY\"\n        const val CM_PACKAGE_NAME_KEY = \"CM_PACKAGE_NAME\"\n        const val CM_BUSINESS_NAME_KEY = \"CM_BUSINESS_NAME\"\n        const val CM_PART_KEY = \"CM_PART\"\n    }\n\n    /**\n     * @param pluginActivity 插件Activity\n     * @return 容器Activity\n     */\n    abstract fun onBindContainerActivity(pluginActivity: ComponentName): ComponentName\n\n    abstract fun onBindContainerContentProvider(pluginContentProvider: ComponentName): ContainerProviderInfo\n\n    override fun startActivity(\n        shadowContext: ShadowContext,\n        pluginIntent: Intent,\n        option: Bundle?\n    ): Boolean {\n        return if (pluginIntent.isPluginComponent()) {\n            shadowContext.superStartActivity(pluginIntent.toActivityContainerIntent(), option)\n            true\n        } else {\n            false\n        }\n    }\n\n\n    override fun startActivityForResult(\n        delegator: GeneratedHostActivityDelegator,\n        pluginIntent: Intent,\n        requestCode: Int,\n        option: Bundle?,\n        callingActivity: ComponentName\n    ): Boolean {\n        return if (pluginIntent.isPluginComponent()) {\n            val containerIntent = pluginIntent.toActivityContainerIntent()\n            containerIntent.putExtra(CM_CALLING_ACTIVITY_KEY, callingActivity)\n            delegator.startActivityForResult(containerIntent, requestCode, option)\n            true\n        } else {\n            false\n        }\n    }\n\n    override fun startService(\n        context: ShadowContext,\n        service: Intent\n    ): Pair<Boolean, ComponentName?> {\n        if (service.isPluginComponent()) {\n            // 插件service intent不需要转换成container service intent，直接使用intent\n            val component = mPluginServiceManager!!.startPluginService(service)\n            if (component != null) {\n                return Pair(true, component)\n            }\n        }\n\n        return Pair(false, service.component)\n\n    }\n\n    override fun stopService(context: ShadowContext, intent: Intent): Pair<Boolean, Boolean> {\n        if (intent.isPluginComponent()) {\n            // 插件service intent不需要转换成container service intent，直接使用intent\n            val stopped = mPluginServiceManager!!.stopPluginService(intent)\n            return Pair(true, stopped)\n        }\n\n\n        return Pair(false, true)\n    }\n\n    override fun bindService(\n        context: ShadowContext,\n        intent: Intent,\n        conn: ServiceConnection,\n        flags: Int\n    ): Pair<Boolean, Boolean> {\n        return if (intent.isPluginComponent()) {\n            // 插件service intent不需要转换成container service intent，直接使用intent\n            mPluginServiceManager!!.bindPluginService(intent, conn, flags)\n            Pair(true, true)\n        } else {\n            Pair(false, false)\n        }\n\n\n    }\n\n    override fun unbindService(\n        context: ShadowContext,\n        conn: ServiceConnection\n    ): Pair<Boolean, Unit> {\n        return Pair.create(\n            mPluginServiceManager!!.unbindPluginService(conn).first,\n            Unit\n        )\n    }\n\n    override fun convertPluginActivityIntent(pluginIntent: Intent): Intent {\n        return if (pluginIntent.isPluginComponent()) {\n            pluginIntent.toActivityContainerIntent()\n        } else {\n            pluginIntent\n        }\n    }\n\n    /**\n     * key:插件Activity类名\n     * value:插件PackageName\n     */\n    private val packageNameMap: MutableMap<String, String> = HashMap()\n\n    /**\n     * key:插件ComponentName\n     * value:壳子ComponentName\n     */\n    private val componentMap: MutableMap<ComponentName, ComponentName> = HashMap()\n\n    /**\n     * key:插件ComponentName\n     * value:LoadParameters\n     */\n    private val loadParametersMap: MutableMap<ComponentName, LoadParameters> = hashMapOf()\n\n    /**\n     * key:插件ComponentName\n     * value:PluginManifest.ActivityInfo\n     */\n    private val pluginActivityInfoMap: MutableMap<ComponentName, PluginManifest.ActivityInfo> =\n        hashMapOf()\n\n    /**\n     * 保存所有已加载插件对PluginManifest和apk文件路径对应关系\n     * 用于在同一个Loader加载对多个插件之间相互查找组件\n     */\n    private val allLoadedPlugin: MutableList<kotlin.Pair<PluginManifest, String>> = mutableListOf()\n\n    fun addPluginApkInfo(\n        pluginManifest: PluginManifest,\n        loadParameters: LoadParameters,\n        archiveFilePath: String\n    ) {\n        fun common(componentInfo: PluginManifest.ComponentInfo, componentName: ComponentName) {\n            packageNameMap[componentInfo.className] = componentName.packageName\n            val previousValue = loadParametersMap.put(componentName, loadParameters)\n            if (previousValue != null) {\n                throw IllegalStateException(\"重复添加Component：$componentName\")\n            }\n        }\n\n        val applicationPackageName = pluginManifest.applicationPackageName\n        pluginManifest.activities?.forEach {\n            val componentName = ComponentName(applicationPackageName, it.className)\n            common(it, componentName)\n            componentMap[componentName] = onBindContainerActivity(componentName)\n            pluginActivityInfoMap[componentName] = it\n        }\n\n        pluginManifest.services?.forEach {\n            val componentName = ComponentName(applicationPackageName, it.className)\n            common(it, componentName)\n        }\n\n        pluginManifest.providers?.forEach {\n            val componentName = ComponentName(applicationPackageName, it.className)\n            mPluginContentProviderManager!!.addContentProviderInfo(\n                loadParameters.partKey,\n                it,\n                onBindContainerContentProvider(componentName)\n            )\n        }\n\n        pluginManifest.receivers?.forEach {\n            val componentName = ComponentName(applicationPackageName, it.className)\n            common(it, componentName)\n        }\n\n        allLoadedPlugin.add(pluginManifest to archiveFilePath)\n    }\n\n    fun getComponentBusinessName(componentName: ComponentName): String? {\n        return loadParametersMap[componentName]?.businessName\n    }\n\n    fun getComponentPartKey(componentName: ComponentName): String? {\n        return loadParametersMap[componentName]?.partKey\n    }\n\n    private var mPluginServiceManager: PluginServiceManager? = null\n    fun setPluginServiceManager(pluginServiceManager: PluginServiceManager) {\n        mPluginServiceManager = pluginServiceManager\n    }\n\n    private var mPluginContentProviderManager: PluginContentProviderManager? = null\n    fun setPluginContentProviderManager(pluginContentProviderManager: PluginContentProviderManager) {\n        mPluginContentProviderManager = pluginContentProviderManager\n    }\n\n    private fun Intent.isPluginComponent(): Boolean {\n        val component = component ?: return false\n        val className = component.className\n        return packageNameMap.containsKey(className)\n    }\n\n    /**\n     * 调用前必须先调用isPluginComponent判断Intent确实一个插件内的组件\n     */\n    private fun Intent.toActivityContainerIntent(): Intent {\n        val bundleForPluginLoader = Bundle()\n        val pluginActivityInfo = pluginActivityInfoMap[component]!!\n        bundleForPluginLoader.putParcelable(CM_ACTIVITY_INFO_KEY, pluginActivityInfo)\n        return toContainerIntent(bundleForPluginLoader)\n    }\n\n\n    /**\n     * 构造pluginIntent对应的ContainerIntent\n     * 调用前必须先调用isPluginComponent判断Intent确实一个插件内的组件\n     */\n    private fun Intent.toContainerIntent(bundleForPluginLoader: Bundle): Intent {\n        val component = this.component\n            ?: throw IllegalArgumentException(\"Activity Intent必须指定ComponentName\")\n        val className = component.className\n\n        val packageName = packageNameMap[className]\n            ?: throw IllegalArgumentException(\"已加载的插件中找不到${className}对应的packageName\")\n        this.component = ComponentName(packageName, className)\n\n        val loadParameters = loadParametersMap[component]\n            ?: throw IllegalArgumentException(\"已加载的插件中找不到${component}对应的LoadParameters\")\n        val businessName = loadParameters.businessName\n        val partKey = loadParameters.partKey\n\n        val pluginExtras: Bundle? = extras\n        replaceExtras(null as Bundle?)\n\n        val containerComponent = componentMap[component]\n            ?: throw IllegalArgumentException(\"已加载的插件中找不到${component}对应的ContainerActivity\")\n        val containerIntent = Intent(this)\n        containerIntent.component = containerComponent\n\n        bundleForPluginLoader.putString(CM_CLASS_NAME_KEY, className)\n        bundleForPluginLoader.putString(CM_PACKAGE_NAME_KEY, packageName)\n\n        containerIntent.putExtra(CM_EXTRAS_BUNDLE_KEY, pluginExtras)\n        containerIntent.putExtra(CM_BUSINESS_NAME_KEY, businessName)\n        containerIntent.putExtra(CM_PART_KEY, partKey)\n        containerIntent.putExtra(CM_LOADER_BUNDLE_KEY, bundleForPluginLoader)\n        containerIntent.putExtra(LOADER_VERSION_KEY, BuildConfig.VERSION_NAME)\n        containerIntent.putExtra(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid)\n        return containerIntent\n    }\n\n    fun getArchiveFilePathForActivity(className: String) =\n        getArchiveFilePath(className, PluginManifest::getActivities)\n\n    fun getArchiveFilePathForService(className: String) =\n        getArchiveFilePath(className, PluginManifest::getServices)\n\n    fun getArchiveFilePathForProviderByAction(action: String?): kotlin.Pair<String?, String?> {\n        for ((pluginManifest, archiveFilePath) in allLoadedPlugin) {\n            val providers = pluginManifest.providers\n            if (providers != null) {\n                for (provider in providers) {\n                    if (action?.equals(provider.authorities) == true) {\n                        return provider.className to archiveFilePath\n                    }\n                }\n            }\n        }\n        return null to null\n    }\n\n    fun getArchiveFilePathForProviderByClassName(className: String): kotlin.Pair<String?, String?> {\n        for ((pluginManifest, archiveFilePath) in allLoadedPlugin) {\n            val providers = pluginManifest.providers\n            if (providers != null) {\n                for (provider in providers) {\n                    if (className == provider.className) {\n                        return provider.className to archiveFilePath\n                    }\n                }\n            }\n        }\n        return null to null\n    }\n\n    fun getAllArchiveFilePaths() = allLoadedPlugin.map { it.second }.toList()\n\n    private fun getArchiveFilePath(\n        className: String,\n        getComponents: (PluginManifest) -> Array<out PluginManifest.ComponentInfo>?\n    ): String? {\n        for ((pluginManifest, archiveFilePath) in allLoadedPlugin) {\n            val components = getComponents(pluginManifest)\n            if (components != null) {\n                for (component in components) {\n                    if (component.className == className) {\n                        return archiveFilePath\n                    }\n                }\n            }\n        }\n        return null\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginContentProviderManager.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.managers\n\nimport android.content.ContentProvider\nimport android.content.Context\nimport android.content.pm.ProviderInfo\nimport android.net.Uri\nimport android.os.Bundle\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo\nimport com.tencent.shadow.core.loader.infos.PluginParts\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport com.tencent.shadow.core.runtime.UriConverter\nimport java.util.*\nimport kotlin.collections.HashSet\nimport kotlin.collections.set\n\nclass PluginContentProviderManager() : UriConverter.UriParseDelegate {\n\n    /**\n     * key : pluginAuthority\n     * value : plugin ContentProvider\n     */\n    private val providerMap = HashMap<String, ContentProvider>()\n\n    /**\n     * key : plugin Authority\n     * value :  containerProvider Authority\n     */\n    private val providerAuthorityMap = HashMap<String, String>()\n\n\n    private val pluginProviderInfoMap = HashMap<String, HashSet<PluginManifest.ProviderInfo>?>()\n\n\n    override fun parse(uriString: String): Uri {\n        if (uriString.startsWith(CONTENT_PREFIX)) {\n            val uriContent = uriString.substring(CONTENT_PREFIX.length)\n            val index = uriContent.indexOf(\"/\")\n            val originalAuthority = if (index != -1) uriContent.substring(0, index) else uriContent\n            val containerAuthority = getContainerProviderAuthority(originalAuthority)\n            if (containerAuthority != null) {\n                return Uri.parse(\"$CONTENT_PREFIX$containerAuthority/$uriContent\")\n            }\n        }\n        return Uri.parse(uriString)\n    }\n\n    override fun parseCall(uriString: String, extra: Bundle): Uri {\n        val pluginUri = parse(uriString)\n        extra.putString(SHADOW_BUNDLE_KEY, pluginUri.toString())\n        return pluginUri\n    }\n\n    fun addContentProviderInfo(\n        partKey: String,\n        pluginProviderInfo: PluginManifest.ProviderInfo,\n        containerProviderInfo: ContainerProviderInfo\n    ) {\n        if (providerMap.containsKey(pluginProviderInfo.authorities)) {\n            throw RuntimeException(\"重复添加 ContentProvider\")\n        }\n\n        providerAuthorityMap[pluginProviderInfo.authorities] = containerProviderInfo.authority\n        var pluginProviderInfos: HashSet<PluginManifest.ProviderInfo>? = null\n        if (pluginProviderInfoMap.containsKey(partKey)) {\n            pluginProviderInfos = pluginProviderInfoMap[partKey]\n        } else {\n            pluginProviderInfos = HashSet()\n        }\n        pluginProviderInfos?.add(pluginProviderInfo)\n        pluginProviderInfoMap.put(partKey, pluginProviderInfos)\n    }\n\n    fun createContentProviderAndCallOnCreate(\n        context: Context,\n        partKey: String,\n        pluginParts: PluginParts?\n    ) {\n        pluginProviderInfoMap[partKey]?.forEach {\n            try {\n                val contentProvider = pluginParts!!.appComponentFactory\n                    .instantiateProvider(pluginParts.classLoader, it.className)\n\n                //convert PluginManifest.ProviderInfo to android.content.pm.ProviderInfo\n                val providerInfo = ProviderInfo()\n                providerInfo.packageName = context.packageName\n                providerInfo.name = it.className\n                providerInfo.authority = it.authorities\n                providerInfo.grantUriPermissions = it.grantUriPermissions\n                contentProvider?.attachInfo(context, providerInfo)\n                providerMap[it.authorities] = contentProvider\n            } catch (e: Exception) {\n                throw RuntimeException(\n                    \"partKey==$partKey className==${it.className} authorities==${it.authorities}\",\n                    e\n                )\n            }\n        }\n\n    }\n\n    fun getPluginContentProvider(pluginAuthority: String): ContentProvider? {\n        return providerMap[pluginAuthority]\n    }\n\n    fun getContainerProviderAuthority(pluginAuthority: String): String? {\n        return providerAuthorityMap[pluginAuthority]\n    }\n\n    fun getAllContentProvider(): Set<ContentProvider> {\n        val contentProviders = hashSetOf<ContentProvider>()\n        providerMap.keys.forEach {\n            contentProviders.add(providerMap[it]!!)\n        }\n        return contentProviders\n    }\n\n    fun convert2PluginUri(uri: Uri): Uri {\n        val containerAuthority: String? = uri.authority\n        if (!providerAuthorityMap.values.contains(containerAuthority)) {\n            throw IllegalArgumentException(\"不能识别的uri Authority:$containerAuthority\")\n        }\n        val uriString = uri.toString()\n        return Uri.parse(uriString.replace(\"$containerAuthority/\", \"\"))\n    }\n\n    fun convert2PluginUri(extra: Bundle): Uri {\n        val uriString = extra.getString(SHADOW_BUNDLE_KEY)\n        extra.remove(SHADOW_BUNDLE_KEY)\n        return convert2PluginUri(Uri.parse(uriString))\n    }\n\n    companion object {\n\n        private val CONTENT_PREFIX = \"content://\"\n        private val SHADOW_BUNDLE_KEY = \"shadow_cp_bundle_key\"\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginPackageManagerImpl.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.managers\n\nimport android.annotation.SuppressLint\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.content.pm.*\nimport com.tencent.shadow.core.runtime.PluginPackageManager\n\n@SuppressLint(\"WrongConstant\")\ninternal class PluginPackageManagerImpl(\n    private val pluginApplicationInfoFromPluginManifest: ApplicationInfo,\n    private val pluginArchiveFilePath: String,\n    private val componentManager: ComponentManager,\n    private val hostPackageManager: PackageManager\n) : PluginPackageManager {\n    override fun getApplicationInfo(packageName: String, flags: Int): ApplicationInfo =\n        if (packageName.isPlugin()) {\n            getPluginApplicationInfo(flags)\n        } else {\n            hostPackageManager.getApplicationInfo(packageName, flags)\n        }\n\n    /**\n     * 所有插件中的各种方法签名的getPackageInfo方法汇总到这里。\n     * 如果包名是插件的，优先返回插件的PackageInfo。\n     * 否则返回从宿主（系统）查询到的PackageInfo。\n     * 直接由getPackageArchiveInfo构造的PackageInfo和从getPackageInfo得到的正常的PackageInfo不完全一致。\n     * 在这里修改它使其尽可能像系统返回的。\n     */\n    private fun getPluginPackageInfoIfPossible(\n        packageName: String,\n        flags: Int,\n        hostPackageInfo: PackageInfo,\n    ): PackageInfo? {\n        return if (packageName.isPlugin()) {\n            val packageInfo = hostPackageManager.getPackageArchiveInfo(pluginArchiveFilePath, flags)\n            if (packageInfo != null) {\n                packageInfo.applicationInfo = getPluginApplicationInfo(flags)\n                packageInfo.permissions = hostPackageInfo.permissions\n                packageInfo.requestedPermissions = hostPackageInfo.requestedPermissions\n            }\n            packageInfo\n        } else {\n            hostPackageInfo\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    override fun getPackageInfo(packageName: String, flags: Int) =\n        getPluginPackageInfoIfPossible(\n            packageName,\n            flags,\n            hostPackageManager.getPackageInfo(packageName, flags)\n        )\n\n    @Suppress(\"DEPRECATION\")\n    @SuppressLint(\"NewApi\")\n    override fun getPackageInfo(versionedPackage: VersionedPackage, flags: Int) =\n        getPluginPackageInfoIfPossible(\n            versionedPackage.packageName,\n            flags,\n            hostPackageManager.getPackageInfo(versionedPackage, flags)\n        )\n\n    @SuppressLint(\"NewApi\")\n    override fun getPackageInfo(packageName: String, flags: PackageManager.PackageInfoFlags) =\n        getPluginPackageInfoIfPossible(\n            packageName,\n            flags.value.toInt(),//FIXME 这里会丢失flags升级到Long新增的标志位\n            hostPackageManager.getPackageInfo(packageName, flags)\n        )\n\n    @SuppressLint(\"NewApi\")\n    override fun getPackageInfo(\n        versionedPackage: VersionedPackage,\n        flags: PackageManager.PackageInfoFlags\n    ) =\n        getPluginPackageInfoIfPossible(\n            versionedPackage.packageName,\n            flags.value.toInt(),//FIXME 这里会丢失flags升级到Long新增的标志位\n            hostPackageManager.getPackageInfo(versionedPackage, flags)\n        )\n\n    override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo? =\n        getComponentInfo(\n            component,\n            flags,\n            ComponentManager::getArchiveFilePathForActivity,\n            PackageManager.GET_ACTIVITIES,\n            { it?.activities },\n            PackageManager::getActivityInfo\n        )\n\n    override fun getServiceInfo(component: ComponentName, flags: Int): ServiceInfo? =\n        getComponentInfo(\n            component,\n            flags,\n            ComponentManager::getArchiveFilePathForService,\n            PackageManager.GET_SERVICES,\n            { it?.services },\n            PackageManager::getServiceInfo\n        )\n\n    override fun getProviderInfo(component: ComponentName, flags: Int): ProviderInfo {\n        val (className, archiveFilePath)\n                = componentManager.getArchiveFilePathForProviderByClassName(component.className)\n        if (archiveFilePath != null) {\n            val packageInfo = hostPackageManager.getPackageArchiveInfo(\n                archiveFilePath, PackageManager.GET_PROVIDERS or flags\n            )\n            val componentInfo = packageInfo?.providers?.find {\n                it.name == className\n            }\n            if (componentInfo != null) {\n                return componentInfo\n            }\n        }\n        return hostPackageManager.getProviderInfo(component, flags)\n    }\n\n    override fun resolveActivity(intent: Intent, flags: Int): ResolveInfo? {\n        val component = intent.component\n        if (component != null) {\n            val activityInfo = getActivityInfo(component, flags)\n            if (activityInfo != null) {\n                val resolveInfo = ResolveInfo()\n                resolveInfo.activityInfo = activityInfo\n                return resolveInfo\n            }\n        }\n        return hostPackageManager.resolveActivity(intent, flags)\n    }\n\n    override fun resolveService(intent: Intent, flags: Int): ResolveInfo? {\n        val component = intent.component\n        if (component != null) {\n            val serviceInfo = getServiceInfo(component, flags)\n            if (serviceInfo != null) {\n                val resolveInfo = ResolveInfo()\n                resolveInfo.serviceInfo = serviceInfo\n                return resolveInfo\n            }\n        }\n        return hostPackageManager.resolveService(intent, flags)\n    }\n\n    override fun getArchiveFilePath() = pluginArchiveFilePath\n\n    private fun <T : ComponentInfo> getComponentInfo(\n        component: ComponentName,\n        flags: Int,\n        getArchiveFilePath: ComponentManager.(String) -> String?,\n        componentGetFlag: Int,\n        getFromPackageInfo: (PackageInfo?) -> Array<T>?,\n        getFromHost: PackageManager.(component: ComponentName, flags: Int) -> T?\n    ): T? {\n\n        if (component.packageName.isPlugin()) {\n            val archiveFilePath = componentManager.getArchiveFilePath(component.className)\n            if (archiveFilePath != null) {\n                val packageInfo = hostPackageManager.getPackageArchiveInfo(\n                    archiveFilePath, componentGetFlag or flags\n                )\n                val componentInfo = getFromPackageInfo(packageInfo)?.find {\n                    it.name == component.className\n                }\n                if (componentInfo != null) {\n                    return componentInfo\n                }\n            }\n        }\n        return hostPackageManager.getFromHost(component, flags)\n    }\n\n    override fun resolveContentProvider(name: String, flags: Int): ProviderInfo? {\n        val (className, archiveFilePath) = componentManager.getArchiveFilePathForProviderByAction(\n            name\n        )\n        if (archiveFilePath != null) {\n            val packageInfo = hostPackageManager.getPackageArchiveInfo(\n                archiveFilePath, PackageManager.GET_PROVIDERS or flags\n            )\n            val componentInfo = packageInfo?.providers?.find {\n                it.name == className\n            }\n            if (componentInfo != null) {\n                return componentInfo\n            }\n        }\n        return hostPackageManager.resolveContentProvider(name, flags)\n    }\n\n    override fun queryContentProviders(processName: String?, uid: Int, flags: Int) =\n        if (processName == null) {\n            val allNormalProviders =\n                hostPackageManager.queryContentProviders(null, 0, flags)\n            val allPluginProviders = allPluginProviders(flags)\n            listOf(allNormalProviders, allPluginProviders).flatten()\n        } else if (processName == pluginApplicationInfoFromPluginManifest.processName &&\n            uid == pluginApplicationInfoFromPluginManifest.uid\n        ) {\n            allPluginProviders(flags)\n        } else {\n            hostPackageManager.queryContentProviders(processName, uid, flags)\n        }\n\n    private fun allPluginProviders(flags: Int): List<ProviderInfo> =\n        componentManager.getAllArchiveFilePaths().flatMap {\n            val packageInfo = hostPackageManager.getPackageArchiveInfo(\n                it,\n                PackageManager.GET_PROVIDERS or flags\n            )\n            packageInfo?.providers?.asList().orEmpty()\n        }\n\n    private fun String.isPlugin() = pluginApplicationInfoFromPluginManifest.packageName == this\n\n    private fun getPluginApplicationInfo(flags: Int): ApplicationInfo {\n        val copy = ApplicationInfo(pluginApplicationInfoFromPluginManifest)\n\n        val needMetaData = flags and PackageManager.GET_META_DATA != 0\n        if (needMetaData) {\n            val packageInfo = hostPackageManager.getPackageArchiveInfo(\n                pluginArchiveFilePath,\n                PackageManager.GET_META_DATA\n            )!!\n            val metaData = packageInfo.applicationInfo.metaData\n            copy.metaData = metaData\n        }\n\n        return copy\n    }\n}"
  },
  {
    "path": "projects/sdk/core/loader/src/main/kotlin/com/tencent/shadow/core/loader/managers/PluginServiceManager.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.managers\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.os.Handler\nimport android.os.IBinder\nimport android.os.Looper\nimport com.tencent.shadow.core.loader.ShadowPluginLoader\nimport com.tencent.shadow.core.loader.classloaders.PluginClassLoader\nimport com.tencent.shadow.core.loader.delegates.ShadowDelegate\nimport com.tencent.shadow.core.runtime.ShadowApplication\nimport com.tencent.shadow.core.runtime.ShadowService\nimport java.util.concurrent.CountDownLatch\n\n/**\n * 插件service管理类，负责插件框架内所有service启动，销毁，生命周期管理\n * Created by jaylanchen on 2018/11/29.\n */\n\nclass PluginServiceManager(mPluginLoader: ShadowPluginLoader, mHostContext: Context) {\n    private val delegate =\n        UnsafePluginServiceManager(mPluginLoader, mHostContext)\n    private val mainThreadHandler = Handler(Looper.getMainLooper())\n\n    private inline fun <reified T> execInMainThread(crossinline action: () -> T): T {\n        if (Thread.currentThread() === Looper.getMainLooper().thread) {\n            return action()\n        } else {\n            val countDownLatch = CountDownLatch(1)\n            val result = arrayOfNulls<T>(1)\n            mainThreadHandler.post {\n                result[0] = action()\n                countDownLatch.countDown()\n            }\n            countDownLatch.await()\n            return result[0] as T\n        }\n    }\n\n    fun startPluginService(service: Intent) =\n        execInMainThread {\n            delegate.startPluginService(service)\n        }\n\n    fun stopPluginService(intent: Intent) =\n        execInMainThread {\n            delegate.stopPluginService(intent)\n        }\n\n    fun bindPluginService(intent: Intent, conn: ServiceConnection, flags: Int) =\n        execInMainThread {\n            delegate.bindPluginService(intent, conn, flags)\n        }\n\n    fun unbindPluginService(conn: ServiceConnection) =\n        execInMainThread {\n            delegate.unbindPluginService(conn)\n        }\n}\n\nprivate open class UnsafePluginServiceManager(\n    private val mPluginLoader: ShadowPluginLoader,\n    private val mHostContext: Context\n) {\n\n    // 保存service的binder\n    private val mServiceBinderMap = HashMap<ComponentName, IBinder?>()\n\n    // service对应ServiceConnection集合\n    private val mServiceConnectionMap = HashMap<ComponentName, HashSet<ServiceConnection>>()\n\n    // ServiceConnection与对应的Intent的集合\n    private val mConnectionIntentMap = HashMap<ServiceConnection, Intent>()\n\n    // 所有已启动的service集合\n    private val mAliveServicesMap = HashMap<ComponentName, ShadowService>()\n\n    // 通过startService启动起来的service集合\n    private val mServiceStartByStartServiceSet = HashSet<ComponentName>()\n\n    // 存在mAliveServicesMap中，且stopService已经调用的service集合\n    private val mServiceStopCalledMap = HashSet<ComponentName>()\n\n    private val allDelegates: Collection<ShadowService>\n        get() = mAliveServicesMap.values\n\n    companion object {\n        private var startId: Int = 0\n        fun getNewStartId(): Int {\n            startId++\n\n            return startId\n        }\n    }\n\n\n    fun startPluginService(intent: Intent): ComponentName? {\n        val componentName = intent.component!!\n\n\n        // 检查所请求的service是否已经存在\n        if (!mAliveServicesMap.containsKey(componentName)) {\n            // 不存在则创建\n            val service = createServiceAndCallOnCreate(intent)\n            mAliveServicesMap[componentName] = service\n            // 通过startService启动集合\n            mServiceStartByStartServiceSet.add(componentName)\n        }\n        mAliveServicesMap[componentName]?.onStartCommand(intent, 0, getNewStartId())\n\n\n        return componentName\n    }\n\n    fun stopPluginService(intent: Intent): Boolean {\n        val componentName = intent.component!!\n\n        if (mAliveServicesMap.containsKey(componentName)) {\n            mServiceStopCalledMap.add(componentName)\n\n            // 看是否需要结束掉该service\n            return destroyServiceIfNeed(componentName)\n        }\n\n        return false\n    }\n\n    fun bindPluginService(intent: Intent, conn: ServiceConnection, flags: Int): Boolean {\n        // todo #25 目前实现未处理flags,后续实现补上\n\n        val componentName = intent.component!!\n\n        // 1. 看要bind的service是否创建并在运行了\n        if (!mAliveServicesMap.containsKey(componentName)) {\n            // 如果还没创建，则创建,并保持\n            val service = createServiceAndCallOnCreate(intent)\n            mAliveServicesMap[componentName] = service\n        }\n\n        val service = mAliveServicesMap[componentName]!!\n\n        // 2. 检查是否该Service之前是否被绑定过了\n        if (!mServiceBinderMap.containsKey(componentName)) {\n            // 还没调用过onBinder,在这里调用\n            mServiceBinderMap[componentName] = service.onBind(intent)\n        }\n\n        // 3. 如果binder不为空，则要回调onServiceConnected\n        mServiceBinderMap[componentName]?.let {\n\n\n            // 检查该connections是否存在了\n            if (mServiceConnectionMap.containsKey(componentName)) {\n\n                if (!mServiceConnectionMap[componentName]!!.contains(conn)) {\n                    // 如果service的bind connection集合中不包含该connection,则加入\n                    mServiceConnectionMap[componentName]!!.add(conn)\n                    mConnectionIntentMap[conn] = intent\n\n\n                    // 回调onServiceConnected\n                    conn.onServiceConnected(componentName, it)\n                } else {\n                    // 已经包含该connection了，说明onServiceConnected已经回调过了，所以这里什么也不用干\n                }\n\n            } else {\n                // 该connection是第一个bind connection\n                val connectionSet = HashSet<ServiceConnection>()\n                connectionSet.add(conn)\n                mServiceConnectionMap[componentName] = connectionSet\n                mConnectionIntentMap[conn] = intent\n\n                // 回调onServiceConnected\n                conn.onServiceConnected(componentName, it)\n            }\n        }\n\n        return true\n\n    }\n\n    fun unbindPluginService(connection: ServiceConnection): Pair<Boolean, Boolean> {\n        var isPluginService = false\n        var isPluginServiceStopped = false\n        for ((componentName, connSet) in mServiceConnectionMap) {\n            if (connSet.contains(connection)) {\n                isPluginService = true\n                connSet.remove(connection)\n                val intent = mConnectionIntentMap.remove(connection)\n\n                if (connSet.size == 0) {\n                    // 已经没有任何connection了，mServiceConnectionMap移除该service数据\n                    mServiceConnectionMap.remove(componentName)\n\n                    // 所有connection都unbind了\n                    mAliveServicesMap[componentName]?.onUnbind(intent!!)\n                }\n\n                // 结束该service\n                isPluginServiceStopped = destroyServiceIfNeed(componentName)\n\n                connection.onServiceDisconnected(componentName)\n\n                break\n            }\n        }\n        return Pair(isPluginService, isPluginServiceStopped)\n    }\n\n\n    fun onConfigurationChanged(newConfig: Configuration?) {\n        allDelegates.forEach {\n            it.onConfigurationChanged(newConfig)\n        }\n    }\n\n    fun onLowMemory() {\n        allDelegates.forEach {\n            it.onLowMemory()\n        }\n    }\n\n    fun onTrimMemory(level: Int) {\n        allDelegates.forEach {\n            it.onTrimMemory(level)\n        }\n    }\n\n\n    fun onTaskRemoved(rootIntent: Intent) {\n        allDelegates.forEach {\n            it.onTaskRemoved(rootIntent)\n        }\n    }\n\n\n    fun onDestroy() {\n        mServiceBinderMap.clear()\n        mServiceConnectionMap.clear()\n        mConnectionIntentMap.clear()\n        mAliveServicesMap.clear()\n        mServiceStartByStartServiceSet.clear()\n        mServiceStopCalledMap.clear()\n    }\n\n\n    private fun createServiceAndCallOnCreate(intent: Intent): ShadowService {\n        val service = newServiceInstance(intent)\n        service.onCreate()\n        return service\n    }\n\n\n    private fun newServiceInstance(intent: Intent): ShadowService {\n        val componentName = intent.component!!\n        val businessName = mPluginLoader.mComponentManager.getComponentBusinessName(componentName)\n        val partKey = mPluginLoader.mComponentManager.getComponentPartKey(componentName)\n        val className = componentName.className\n\n        val tmpShadowDelegate = TmpShadowDelegate()\n        mPluginLoader.inject(tmpShadowDelegate, partKey!!)\n        val service = tmpShadowDelegate.getAppComponentFactory()\n            .instantiateService(tmpShadowDelegate.getPluginClassLoader(), className, intent)\n\n        service.setPluginResources(tmpShadowDelegate.getPluginResources())\n        service.setPluginClassLoader(tmpShadowDelegate.getPluginClassLoader())\n        service.setShadowApplication(tmpShadowDelegate.getPluginApplication())\n        service.setPluginComponentLauncher(tmpShadowDelegate.getComponentManager())\n        service.applicationInfo = tmpShadowDelegate.getPluginApplication().applicationInfo\n        service.setBusinessName(businessName)\n        service.setPluginPartKey(partKey)\n\n        //和ShadowActivityDelegate.initPluginActivity一样，attachBaseContext放到最后\n        service.setHostContextAsBase(mHostContext)\n        return service\n    }\n\n\n    private fun destroyServiceIfNeed(service: ComponentName): Boolean {\n\n        val destroy = {\n            // 移除该service，及相关数据\n            val serviceDelegate = mAliveServicesMap.remove(service)\n            mServiceStopCalledMap.remove(service)\n            mServiceBinderMap.remove(service)\n            mServiceStartByStartServiceSet.remove(service)\n\n            // 调用service的onDestroy\n            serviceDelegate!!.onDestroy()\n        }\n\n        // 如果不是通过startService启动的，则所有connection unbind后就可以结束了\n        if (!mServiceStartByStartServiceSet.contains(service)) {\n            if (mServiceConnectionMap[service] == null) {\n                // 结束该service\n                destroy()\n                return true\n            }\n        } else {\n            // 如果该service，有通过startService,则必须调用过stopService且没有bind了，才能销毁\n            if (mServiceStopCalledMap.contains(service) && !mServiceConnectionMap.containsKey(\n                    service\n                )\n            ) {\n                // 结束该service\n                destroy()\n                return true\n            }\n\n        }\n\n        return false\n    }\n\n}\n\nprivate class TmpShadowDelegate : ShadowDelegate() {\n\n    fun getPluginApplication(): ShadowApplication = mPluginApplication\n    fun getAppComponentFactory() = mAppComponentFactory\n    fun getPluginClassLoader(): PluginClassLoader = mPluginClassLoader\n    fun getPluginResources(): Resources = mPluginResources\n    fun getComponentManager(): ComponentManager = mComponentManager\n}\n"
  },
  {
    "path": "projects/sdk/core/loader/src/test/kotlin/com/tencent/shadow/core/loader/classloaders/PluginClassLoaderTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.loader.classloaders\n\nimport org.junit.Assert\nimport org.junit.Test\n\n/**\n * @author zby\n * @email linxy59@mail2.sysu.edu.cn\n * @date 2019-09-06\n * @description test String.inPackage(packageNames: Array<String>): Boolean\n * @usage click icon on the left of testString_inPackage()\n */\nclass PluginClassLoaderTest {\n\n    @Test\n    fun case11() {\n        val packageNames = arrayOf(\"a.b.c\")\n        val className = \"a.b.c.D\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case12() {\n        val packageNames = arrayOf(\"a.b.c\")\n        val className = \"a.b.D\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case13() {\n        val packageNames = arrayOf(\"a.b.c\")\n        val className = \"a.b.c\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case14() {\n        val packageNames = arrayOf(\"a.b.c\")\n        val className = \"a.b.c.d.E\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case15() {\n        val packageNames = arrayOf(\"xxxx\", \"a.b.c\")\n        val className = \"a.b.c.D\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case16() {\n        val packageNames = arrayOf(\"a.b.c\", \"xxxx\")\n        val className = \"a.b.c.D\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case21() {\n        val packageNames = arrayOf(\"\")\n        val className = \"A\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case22() {\n        val packageNames = arrayOf(\"\")\n        val className = \"a.B\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case31() {\n        val packageNames = arrayOf(\"b.c\")\n        val className = \"a.b.c.D\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case41() {\n        val packageNames = arrayOf(\"a.b.c.*\")\n        val className = \"a.b.c.D\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case42() {\n        val packageNames = arrayOf(\"a.b.c.*\")\n        val className = \"a.b.c\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case43() {\n        val packageNames = arrayOf(\"a.b.c.*\")\n        val className = \"a.b.c.d.E\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case44() {\n        val packageNames = arrayOf(\"a.b.c.*\")\n        val className = \"a.b.c.d.e.F\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case51() {\n        val packageNames = arrayOf(\".*\")\n        val className = \"a.b.c.d.E\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case52() {\n        val packageNames = arrayOf(\".*\")\n        val className = \"A\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case61() {\n        val packageNames = arrayOf(\"a.b.c.**\")\n        val className = \"a.b.c.D\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case62() {\n        val packageNames = arrayOf(\"a.b.c.**\")\n        val className = \"a.b.c.d.E\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case63() {\n        val packageNames = arrayOf(\"a.b.c.**\")\n        val className = \"a.b.c.d.e.F\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case64() {\n        val packageNames = arrayOf(\"a.b.c.**\")\n        val className = \"a.b.C\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case65() {\n        val packageNames = arrayOf(\".**\")\n        val className = \"a.b.c.D\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case66() {\n        val packageNames = arrayOf(\".**\")\n        val className = \"a.B\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case67() {\n        val packageNames = arrayOf(\".**\")\n        val className = \"A\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case68() {\n        val packageNames = arrayOf(\"a.b.c.d.**\", \"a.b.c2.d2.**\")\n        val className = \"a.b.c2.d2.e2.F\"\n        Assert.assertTrue(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case71() {\n        val packageNames = arrayOf(\"com.tencent.**\")\n        val className = \"com.tencentshadow.MyClass\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case72() {\n        val packageNames = arrayOf(\"com.tencent.**\")\n        val className = \"com.tencentshadow.next.MyClass\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case73() {\n        val packageNames = arrayOf(\"com.tencent**\")\n        val className = \"com.tencentshadow.MyClass\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case74() {\n        val packageNames = arrayOf(\"com.tencent**\")\n        val className = \"com.tencentshadow.next.MyClass\"\n        Assert.assertFalse(className.inPackage(packageNames))\n    }\n\n    @Test\n    fun case75() {\n        //允许retrofit2包中的类和retrofit2包中所有子包中的类\n        val packageNames = arrayOf(\"retrofit2\", \"retrofit2.**\")\n        val className1 = \"retrofit2.Retrofit\\$Builder\"\n        val className2 = \"retrofit2.a.Retrofit\\$Builder\"\n        val className3 = \"retrofit2.a.b.Retrofit\\$Builder\"\n        Assert.assertTrue(className1.inPackage(packageNames))\n        Assert.assertTrue(className2.inPackage(packageNames))\n        Assert.assertTrue(className3.inPackage(packageNames))\n    }\n}\n\n\n"
  },
  {
    "path": "projects/sdk/core/manager/.gitignore",
    "content": "/build\n*.iml\n"
  },
  {
    "path": "projects/sdk/core/manager/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    implementation project(':utils')\n    compileOnly project(':common')\n    api project(':load-parameters')\n\n    testImplementation \"junit:junit:$junit_version\"\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/BasePluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Pair;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.manager.installplugin.AppCacheFolderManager;\nimport com.tencent.shadow.core.manager.installplugin.CopySoBloc;\nimport com.tencent.shadow.core.manager.installplugin.InstallPluginException;\nimport com.tencent.shadow.core.manager.installplugin.InstalledDao;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPluginDBHelper;\nimport com.tencent.shadow.core.manager.installplugin.InstalledType;\nimport com.tencent.shadow.core.manager.installplugin.ODexBloc;\nimport com.tencent.shadow.core.manager.installplugin.PluginConfig;\nimport com.tencent.shadow.core.manager.installplugin.SafeZipFile;\nimport com.tencent.shadow.core.manager.installplugin.UnpackManager;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\npublic abstract class BasePluginManager {\n    private static final Logger mLogger = LoggerFactory.getLogger(BasePluginManager.class);\n    /*\n     * 宿主的context对象\n     */\n    public Context mHostContext;\n\n    /**\n     * 从压缩包中将插件解压出来，解析成InstalledPlugin\n     */\n    private UnpackManager mUnpackManager;\n\n    /**\n     * 插件信息查询数据库接口\n     */\n    final private InstalledDao mInstalledDao;\n\n    /**\n     * UI线程的handler\n     */\n    protected Handler mUiHandler = new Handler(Looper.getMainLooper());\n\n\n    public BasePluginManager(Context context) {\n        this.mHostContext = context.getApplicationContext();\n        this.mUnpackManager = new UnpackManager(mHostContext.getFilesDir(), getName());\n        this.mInstalledDao = new InstalledDao(new InstalledPluginDBHelper(mHostContext, getName()));\n    }\n\n    /**\n     * PluginManager的名字\n     * 用于和其他PluginManager区分持续化存储的名字\n     */\n    abstract protected String getName();\n\n    /**\n     * 从文件夹中解压插件\n     *\n     * @param dir 文件夹路径\n     * @return PluginConfig\n     */\n    public final PluginConfig installPluginFromDir(File dir) {\n        throw new UnsupportedOperationException(\"TODO\");\n    }\n\n    /**\n     * 从压缩包中解压插件\n     *\n     * @param zip  压缩包路径\n     * @param hash 压缩包hash\n     * @return PluginConfig\n     */\n    public final PluginConfig installPluginFromZip(File zip, String hash) throws IOException, JSONException {\n        String zipHash;\n        if (hash != null) {\n            zipHash = hash;\n        } else {\n            zipHash = mUnpackManager.zipHash(zip);\n        }\n        File pluginUnpackDir = mUnpackManager.getPluginUnpackDir(zipHash, zip);\n        JSONObject configJson = mUnpackManager.getConfigJson(zip);\n        PluginConfig pluginConfig = PluginConfig.parseFromJson(configJson, pluginUnpackDir);\n\n        if (!pluginConfig.isUnpacked()) {\n            mUnpackManager.unpackPlugin(zip, pluginUnpackDir);\n        }\n\n        return pluginConfig;\n    }\n\n    /**\n     * 安装完成时调用\n     * <p>\n     * 将插件信息持久化到数据库\n     *\n     * @param pluginConfig 插件配置信息\n     * @param soDirMap     key:type+partKey\n     */\n    public final void onInstallCompleted(PluginConfig pluginConfig,\n                                         Map<String, String> soDirMap) {\n        File root = mUnpackManager.getAppDir();\n        String oDexDir = ODexBloc.isEffective() ?\n                AppCacheFolderManager.getODexDir(root, pluginConfig.UUID).getAbsolutePath() : null;\n\n        //在API 33以上的系统上，禁止动态加载文件可写入，满足系统安全限制\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {\n            setWritableFalseForPluginFiles(pluginConfig);\n        }\n\n        mInstalledDao.insert(pluginConfig, soDirMap, oDexDir);\n    }\n\n    private static void setWritableFalseForPluginFiles(PluginConfig pluginConfig) {\n        List<PluginConfig.FileInfo> list = new ArrayList<>();\n        list.add(pluginConfig.pluginLoader);\n        list.add(pluginConfig.runTime);\n        list.addAll(pluginConfig.plugins.values());\n        for (PluginConfig.FileInfo fileInfo : list) {\n            //noinspection ResultOfMethodCallIgnored\n            fileInfo.file.setWritable(false);\n        }\n    }\n\n    protected InstalledPlugin.Part getPluginPartByPartKey(String uuid, String partKey) {\n        InstalledPlugin installedPlugin = mInstalledDao.getInstalledPluginByUUID(uuid);\n        if (installedPlugin == null) {\n            throw new RuntimeException(\"没有找到uuid:\" + uuid);\n        }\n        InstalledPlugin.Part part = installedPlugin.getPart(partKey);\n        if (part == null) {\n            throw new RuntimeException(\"没有找到Part partKey:\" + partKey);\n        }\n        return part;\n    }\n\n    protected InstalledPlugin getInstalledPlugin(String uuid) {\n        return mInstalledDao.getInstalledPluginByUUID(uuid);\n    }\n\n    protected InstalledPlugin.Part getLoaderOrRunTimePart(String uuid, int type) {\n        if (type != InstalledType.TYPE_PLUGIN_LOADER && type != InstalledType.TYPE_PLUGIN_RUNTIME) {\n            throw new RuntimeException(\"不支持的type:\" + type);\n        }\n        InstalledPlugin installedPlugin = mInstalledDao.getInstalledPluginByUUID(uuid);\n        if (type == InstalledType.TYPE_PLUGIN_RUNTIME) {\n            if (installedPlugin.runtimeFile != null) {\n                return installedPlugin.runtimeFile;\n            }\n        } else if (type == InstalledType.TYPE_PLUGIN_LOADER) {\n            if (installedPlugin.pluginLoaderFile != null) {\n                return installedPlugin.pluginLoaderFile;\n            }\n        }\n        throw new RuntimeException(\"没有找到Part type :\" + type);\n    }\n\n    /**\n     * odex优化\n     *\n     * @param uuid    插件包的uuid\n     * @param partKey 要oDex的插件partkey\n     */\n    public final void oDexPlugin(String uuid, String partKey, File apkFile) throws InstallPluginException {\n        if (!ODexBloc.isEffective()) {\n            return;\n        }\n\n        try {\n            File root = mUnpackManager.getAppDir();\n            File oDexDir = AppCacheFolderManager.getODexDir(root, uuid);\n            ODexBloc.oDexPlugin(apkFile, oDexDir, AppCacheFolderManager.getODexCopiedFile(oDexDir, partKey));\n        } catch (InstallPluginException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"oDexPlugin exception:\", e);\n            }\n            throw e;\n        }\n    }\n\n\n    /**\n     * odex优化\n     *\n     * @param uuid    插件包的uuid\n     * @param type    要oDex的插件类型 @class IntalledType  loader or runtime\n     * @param apkFile 插件apk文件\n     */\n    public final void oDexPluginLoaderOrRunTime(String uuid, int type, File apkFile) throws InstallPluginException {\n        if (!ODexBloc.isEffective()) {\n            return;\n        }\n\n        try {\n            File root = mUnpackManager.getAppDir();\n            File oDexDir = AppCacheFolderManager.getODexDir(root, uuid);\n            String key = type == InstalledType.TYPE_PLUGIN_LOADER ? \"loader\" : \"runtime\";\n            ODexBloc.oDexPlugin(apkFile, oDexDir, AppCacheFolderManager.getODexCopiedFile(oDexDir, key));\n        } catch (InstallPluginException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"oDexPluginLoaderOrRunTime exception:\", e);\n            }\n            throw e;\n        }\n    }\n\n\n    /**\n     * 解压插件apk中的so。\n     * <p>\n     * 插件的ABI和宿主正在使用的保持一致。\n     * 注意：如果宿主没有打包so，它的ABI会被系统自动设置为设备默认值，\n     * 默认值可能和插件apk中打包的ABI不一致，导致插件so解压不正确。\n     *\n     * @param uuid    插件包的uuid\n     * @param partKey 要解压so的插件partkey\n     * @param apkFile 插件apk文件\n     * @return soDirMap条目\n     */\n    public final Pair<String, String> extractSo(String uuid, String partKey, File apkFile) throws InstallPluginException {\n        try {\n            File root = mUnpackManager.getAppDir();\n            File soDir = AppCacheFolderManager.getLibDir(root, uuid);\n            String soDirMapKey = InstalledType.TYPE_PLUGIN + partKey;\n            String soDirPath = soDir.getAbsolutePath();\n\n            String pluginPreferredAbi = getPluginPreferredAbi(getPluginSupportedAbis(), apkFile);\n            if (pluginPreferredAbi.isEmpty()) {\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"插件没有so\");\n                }\n            } else {\n                String filter = \"lib/\" + pluginPreferredAbi + \"/\";\n\n                // 插件如果设置了android:extractNativeLibs=\"false\"，则不需要解压出so\n                boolean needExtractNativeLibs = needExtractNativeLibs(apkFile, filter);\n\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"extractSo uuid=={} partKey=={} apkFile=={} soDir=={} filter=={} needExtractNativeLibs=={}\",\n                            uuid, partKey, apkFile.getAbsolutePath(), soDir.getAbsolutePath(), filter, needExtractNativeLibs);\n                }\n\n                if (needExtractNativeLibs) {\n                    CopySoBloc.copySo(apkFile, soDir\n                            , AppCacheFolderManager.getLibCopiedFile(soDir, partKey), filter);\n                } else {\n                    soDirPath = apkFile.getAbsolutePath() + \"!/\" + filter;\n                }\n            }\n            return new Pair<>(soDirMapKey, soDirPath);\n        } catch (InstallPluginException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"extractSo exception:\", e);\n            }\n            throw e;\n        }\n    }\n\n    /**\n     * 插件apk的so解压\n     *\n     * @param uuid    插件包的uuid\n     * @param type    要oDex的插件类型 @class IntalledType  loader or runtime\n     * @param apkFile 插件apk文件\n     * @return soDirMap条目\n     */\n    public final Pair<String, String> extractLoaderOrRunTimeSo(String uuid,\n                                                               int type,\n                                                               File apkFile)\n            throws InstallPluginException {\n        try {\n            File root = mUnpackManager.getAppDir();\n            String key = type == InstalledType.TYPE_PLUGIN_LOADER ? \"loader\" : \"runtime\";\n            String pluginPreferredAbi = getPluginPreferredAbi(getPluginSupportedAbis(), apkFile);\n            String filter = \"lib/\" + pluginPreferredAbi + \"/\";\n            File soDir = AppCacheFolderManager.getLibDir(root, uuid);\n\n            if (pluginPreferredAbi.isEmpty()) {\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(key + \"没有so\");\n                }\n            } else {\n                CopySoBloc.copySo(apkFile, soDir\n                        , AppCacheFolderManager.getLibCopiedFile(soDir, key), filter);\n            }\n\n            String soDirMapKey = Integer.toString(type) + null;// 同InstalledDao.parseConfig\n            String soDirPath = soDir.getAbsolutePath();\n            return new Pair<>(soDirMapKey, soDirPath);\n        } catch (InstallPluginException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"extractLoaderOrRunTimeSo exception:\", e);\n            }\n            throw e;\n        }\n    }\n\n\n    /**\n     * 获取已安装的插件，最后安装的排在返回List的最前面\n     *\n     * @param limit 最多获取个数\n     */\n    public final List<InstalledPlugin> getInstalledPlugins(int limit) {\n        return mInstalledDao.getLatestPlugins(limit);\n    }\n\n\n    /**\n     * 删除指定uuid的插件\n     *\n     * @param uuid 插件包的uuid\n     * @return 是否全部执行成功\n     */\n    public boolean deleteInstalledPlugin(String uuid) {\n        InstalledPlugin installedPlugin = mInstalledDao.getInstalledPluginByUUID(uuid);\n        boolean suc = true;\n        if (installedPlugin.runtimeFile != null) {\n            if (!deletePart(installedPlugin.runtimeFile)) {\n                suc = false;\n            }\n        }\n        if (installedPlugin.pluginLoaderFile != null) {\n            if (!deletePart(installedPlugin.pluginLoaderFile)) {\n                suc = false;\n            }\n        }\n        for (Map.Entry<String, InstalledPlugin.PluginPart> plugin : installedPlugin.plugins.entrySet()) {\n            if (!deletePart(plugin.getValue())) {\n                suc = false;\n            }\n        }\n        if (mInstalledDao.deleteByUUID(uuid) <= 0) {\n            suc = false;\n        }\n        return suc;\n    }\n\n    private boolean deletePart(InstalledPlugin.Part part) {\n        boolean suc = true;\n        if (!deleteFileOrDirectory(part.pluginFile)) {\n            suc = false;\n        }\n        if (part.oDexDir != null && !deleteFileOrDirectory(part.oDexDir)) {\n            suc = false;\n        }\n        if (part.libraryDir != null && !deleteFileOrDirectory(part.libraryDir)) {\n            suc = false;\n        }\n        return suc;\n    }\n\n    /**\n     * 删除文件或目录,递归删除\n     *\n     * @param fileOrDirectory 文件或目录\n     * @return 是否删除成功\n     */\n    private boolean deleteFileOrDirectory(File fileOrDirectory) {\n        if (fileOrDirectory == null || !fileOrDirectory.exists()) {\n            return true;\n        }\n        if (fileOrDirectory.isDirectory()) {\n            File[] files = fileOrDirectory.listFiles();\n            if (files != null) {\n                for (File file : files) {\n                    if (!deleteFileOrDirectory(file)) {\n                        return false;\n                    }\n                }\n            }\n        }\n        return fileOrDirectory.delete();\n    }\n\n    /**\n     * 当前插件希望采用的ABI。\n     * 子类可以override重新决定。\n     *\n     * @param pluginSupportedAbis 从getPluginSupportedAbis方法得到的可选ABI列表\n     * @param apkFile             插件apk文件\n     * @return 最终决定的ABI。插件没有so时返回空字符串。\n     * @throws InstallPluginException 读取apk文件失败时抛出\n     */\n    protected String getPluginPreferredAbi(String[] pluginSupportedAbis, File apkFile)\n            throws InstallPluginException {\n        try (ZipFile zipFile = new SafeZipFile(apkFile)) {\n            //找出插件apk中lib目录下都有哪些子目录\n            Set<String> subDirsInLib = new LinkedHashSet<>();\n            Enumeration<? extends ZipEntry> entries = zipFile.entries();\n            while (entries.hasMoreElements()) {\n                ZipEntry entry = entries.nextElement();\n                String name = entry.getName();\n                if (name.startsWith(\"lib/\")) {\n                    String[] split = name.split(\"/\");\n                    if (split.length == 3) {// like \"lib/arm64-v8a/libabc.so\"\n                        subDirsInLib.add(split[1]);\n                    }\n                }\n            }\n\n            for (String supportedAbi : pluginSupportedAbis) {\n                if (subDirsInLib.contains(supportedAbi)) {\n                    return supportedAbi;\n                }\n            }\n            return \"\";\n        } catch (IOException e) {\n            throw new InstallPluginException(\"读取apk失败，apkFile==\" + apkFile, e);\n        }\n    }\n\n    /**\n     * 获取可用的ABI列表。\n     * 和Build.SUPPORTED_ABIS的区别是，这是宿主已经决定了当前进程用32位so还是64位so了，\n     * 所以可用的ABI只能是其中一部分。\n     */\n    private String[] getPluginSupportedAbis() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            String nativeLibraryDir = mHostContext.getApplicationInfo().nativeLibraryDir;\n            int nextIndexOfLastSlash = nativeLibraryDir.lastIndexOf('/') + 1;\n            String instructionSet = nativeLibraryDir.substring(nextIndexOfLastSlash);\n            if (!isKnownInstructionSet(instructionSet)) {\n                throw new IllegalStateException(\"不认识的instructionSet==\" + instructionSet);\n            }\n            boolean is64Bit = is64BitInstructionSet(instructionSet);\n            return is64Bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS;\n        } else {\n            String cpuAbi = Build.CPU_ABI;\n            String cpuAbi2 = Build.CPU_ABI2;\n            ArrayList<String> list = new ArrayList<>(2);\n            if (cpuAbi != null && !cpuAbi.isEmpty()) {\n                list.add(cpuAbi);\n            }\n            if (cpuAbi2 != null && !cpuAbi2.isEmpty()) {\n                list.add(cpuAbi2);\n            }\n            return list.toArray(new String[0]);\n        }\n    }\n\n    /**\n     * 根据VMRuntime.ABI_TO_INSTRUCTION_SET_MAP\n     */\n    private static boolean isKnownInstructionSet(String instructionSet) {\n        return \"arm\".equals(instructionSet) ||\n                \"mips\".equals(instructionSet) ||\n                \"mips64\".equals(instructionSet) ||\n                \"x86\".equals(instructionSet) ||\n                \"x86_64\".equals(instructionSet) ||\n                \"arm64\".equals(instructionSet);\n    }\n\n    /**\n     * Returns whether the given {@code instructionSet} is 64 bits.\n     *\n     * @param instructionSet a string representing an instruction set.\n     * @return true if given {@code instructionSet} is 64 bits, false otherwise.\n     * <p>\n     * copy from VMRuntime.java\n     */\n    private static boolean is64BitInstructionSet(String instructionSet) {\n        return \"arm64\".equals(instructionSet) ||\n                \"x86_64\".equals(instructionSet) ||\n                \"mips64\".equals(instructionSet);\n    }\n\n    private static boolean needExtractNativeLibs(File apkFile, String filter) throws InstallPluginException {\n        //android:extractNativeLibs是API 23引入的\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            return true;\n        }\n        try (ZipFile zipFile = new SafeZipFile(apkFile)) {\n            Enumeration<? extends ZipEntry> entries = zipFile.entries();\n            while (entries.hasMoreElements()) {\n                ZipEntry entry = entries.nextElement();\n                String name = entry.getName();\n                if (name.startsWith(filter)) {\n                    return entry.getMethod() != ZipEntry.STORED;\n                }\n            }\n            return false;\n        } catch (IOException e) {\n            throw new InstallPluginException(\"读取apk失败，apkFile==\" + apkFile, e);\n        }\n    }\n\n    /**\n     * 释放资源\n     */\n    public void close() {\n        mInstalledDao.close();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/AppCacheFolderManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport java.io.File;\n\n/**\n * 目录各模块的目录关系管理\n */\npublic class AppCacheFolderManager {\n\n    public static File getVersionDir(File root, String appName, String version) {\n        return new File(getAppDir(root, appName), version);\n    }\n\n    public static File getAppDir(File root, String appName) {\n        return new File(root, appName);\n    }\n\n\n    public static File getODexDir(File root, String key) {\n        return new File(getODexRootDir(root), key + \"_odex\");\n    }\n\n    public static File getODexCopiedFile(File oDexDir, String key) {\n        return new File(oDexDir, key + \"_oDexed\");\n    }\n\n\n    private static File getODexRootDir(File root) {\n        return new File(root, \"oDex\");\n    }\n\n    public static File getLibDir(File root, String key) {\n        return new File(getLibRootDir(root), key + \"_lib\");\n    }\n\n    public static File getLibCopiedFile(File soDir, String key) {\n        return new File(soDir, key + \"_copied\");\n    }\n\n\n    private static File getLibRootDir(File root) {\n        return new File(root, \"lib\");\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/CopySoBloc.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\n\nimport android.text.TextUtils;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Enumeration;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n\npublic class CopySoBloc {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(CopySoBloc.class);\n\n    private static ConcurrentHashMap<String, Object> sLocks = new ConcurrentHashMap<>();\n\n    public static void copySo(File apkFile, File soDir, File copiedTagFile, String filter) throws InstallPluginException {\n        String key = apkFile.getAbsolutePath();\n        Object lock = sLocks.get(key);\n        if (lock == null) {\n            lock = new Object();\n            sLocks.put(key, lock);\n        }\n\n        synchronized (lock) {\n\n            if (TextUtils.isEmpty(filter) || copiedTagFile.exists()) {\n                return;\n            }\n\n            //如果so目录存在但是个文件，不是目录，那超出预料了。删除了也不一定能工作正常。\n            if (soDir.exists() && soDir.isFile()) {\n                throw new InstallPluginException(\"soDir=\" + soDir.getAbsolutePath() + \"已存在，但它是个文件，不敢贸然删除\");\n            }\n\n            //创建so目录\n            soDir.mkdirs();\n\n            ZipFile zipFile = null;\n            try {\n                zipFile = new SafeZipFile(apkFile);\n                Enumeration<? extends ZipEntry> entries = zipFile.entries();\n                while (entries.hasMoreElements()) {\n                    ZipEntry entry = entries.nextElement();\n                    if (entry.getName().startsWith(filter)) {\n                        String fileName = entry.getName().substring(filter.length());\n                        MinFileUtils.writeOutZipEntry(zipFile, entry, soDir, fileName);\n                    }\n                }\n\n                // 外边创建完成标记\n                try {\n                    copiedTagFile.createNewFile();\n                } catch (IOException e) {\n                    throw new InstallPluginException(\"创建so复制完毕 创建tag文件失败：\" + copiedTagFile.getAbsolutePath(), e);\n                }\n\n            } catch (Exception e) {\n                throw new InstallPluginException(\"解压so 失败 apkFile:\" + apkFile.getAbsolutePath() + \" abi:\" + filter, e);\n            } finally {\n                try {\n                    if (zipFile != null) {\n                        zipFile.close();\n                    }\n                } catch (IOException e) {\n                    mLogger.warn(\"zip关闭时出错忽略\", e);\n                }\n            }\n        }\n    }\n\n\n}\n\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstallPluginException.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\npublic class InstallPluginException extends Exception {\n\n    public InstallPluginException(String message) {\n        super(message);\n    }\n\n    public InstallPluginException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstalledDao.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport android.annotation.SuppressLint;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class InstalledDao {\n\n    final private InstalledPluginDBHelper mDBHelper;\n\n    public InstalledDao(InstalledPluginDBHelper dbHelper) {\n        mDBHelper = dbHelper;\n    }\n\n    /**\n     * 根据插件配置信息插入一组数据\n     *\n     * @param pluginConfig 插件配置信息\n     * @param soDirMap     key:type+partKey\n     * @param oDexDir\n     */\n    public void insert(PluginConfig pluginConfig, Map<String, String> soDirMap, String oDexDir) {\n        SQLiteDatabase db = mDBHelper.getWritableDatabase();\n        if (soDirMap == null) {\n            soDirMap = Collections.emptyMap();\n        }\n        List<ContentValues> contentValuesList = parseConfig(pluginConfig, soDirMap, oDexDir);\n        db.beginTransaction();\n        try {\n            for (ContentValues contentValues : contentValuesList) {\n                db.replace(InstalledPluginDBHelper.TABLE_NAME_MANAGER, null, contentValues);\n            }\n            //把最后一次uuid的插件安装时间作为所有相同uuid的插件的安装时间\n            ContentValues values = new ContentValues();\n            values.put(InstalledPluginDBHelper.COLUMN_INSTALL_TIME, pluginConfig.storageDir.lastModified());\n            db.update(InstalledPluginDBHelper.TABLE_NAME_MANAGER, values, InstalledPluginDBHelper.COLUMN_UUID + \" = ?\", new String[]{pluginConfig.UUID});\n\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n    }\n\n    /**\n     * 删除UUID相关的数据\n     *\n     * @param UUID 插件的发布id\n     * @return 影响的数据行数\n     */\n    public int deleteByUUID(String UUID) {\n        int row;\n        SQLiteDatabase db = mDBHelper.getWritableDatabase();\n        db.beginTransaction();\n        try {\n            row = db.delete(InstalledPluginDBHelper.TABLE_NAME_MANAGER, InstalledPluginDBHelper.COLUMN_UUID + \" =?\", new String[]{UUID});\n            db.setTransactionSuccessful();\n        } finally {\n            db.endTransaction();\n        }\n        return row;\n    }\n\n    /**\n     * 根据uuid和APPID获取对应的插件信息\n     *\n     * @param uuid 插件的发布id\n     * @return 插件安装数据\n     */\n    @SuppressLint(\"Range\")\n    public InstalledPlugin getInstalledPluginByUUID(String uuid) {\n        SQLiteDatabase db = mDBHelper.getReadableDatabase();\n        Cursor cursor = db.query(\n                InstalledPluginDBHelper.TABLE_NAME_MANAGER,\n                null,\n                InstalledPluginDBHelper.COLUMN_UUID + \" = ?\",\n                new String[]{uuid},\n                null,\n                null,\n                null\n        );\n        InstalledPlugin installedPlugin = new InstalledPlugin();\n        installedPlugin.UUID = uuid;\n        while (cursor.moveToNext()) {\n            int type = cursor.getInt(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_TYPE));\n            if (type == InstalledType.TYPE_UUID) {\n                installedPlugin.UUID_NickName = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_VERSION));\n            } else {\n                File pluginFile = new File(cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_PATH)));\n                String oDexPath = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_PLUGIN_ODEX));\n                File oDexDir = oDexPath == null ? null : new File(oDexPath);\n                String libPath = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_PLUGIN_LIB));\n                File libDir = libPath == null ? null : new File(libPath);\n                if (type == InstalledType.TYPE_PLUGIN_LOADER) {\n                    installedPlugin.pluginLoaderFile = new InstalledPlugin.Part(type, pluginFile, oDexDir, libDir);\n                } else if (type == InstalledType.TYPE_PLUGIN_RUNTIME) {\n                    installedPlugin.runtimeFile = new InstalledPlugin.Part(type, pluginFile, oDexDir, libDir);\n                } else {\n                    String businessName = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_BUSINESS_NAME));\n\n                    String partKey = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_PARTKEY));\n\n                    if (type == InstalledType.TYPE_PLUGIN) {\n                        String[] dependsOn = getArrayStringByColumnName(InstalledPluginDBHelper.COLUMN_DEPENDSON, cursor);\n                        String[] hostWhiteList = getArrayStringByColumnName(InstalledPluginDBHelper.COLUMN_HOST_WHITELIST, cursor);\n                        installedPlugin.plugins.put(partKey, new InstalledPlugin.PluginPart(type, businessName, pluginFile, oDexDir, libDir, dependsOn, hostWhiteList));\n                    } else {\n                        throw new RuntimeException(\"出现不认识的type==\" + type);\n                    }\n                }\n            }\n        }\n        cursor.close();\n        return installedPlugin;\n    }\n\n    private String[] getArrayStringByColumnName(String columnName, Cursor cursor) {\n        int columnIndex = cursor.getColumnIndex(columnName);\n        boolean hasColumn = !cursor.isNull(columnIndex);\n        String[] arrayString;\n        if (hasColumn) {\n            String string = cursor.getString(columnIndex);\n            try {\n                JSONArray jsonArray = new JSONArray(string);\n                arrayString = new String[jsonArray.length()];\n                for (int i = 0; i < jsonArray.length(); i++) {\n                    arrayString[i] = jsonArray.getString(i);\n                }\n            } catch (JSONException e) {\n                throw new RuntimeException(e);\n            }\n        } else {\n            arrayString = null;\n        }\n        return arrayString;\n    }\n\n    /**\n     * @deprecated 方法名拼写错误\n     */\n    @Deprecated\n    public List<InstalledPlugin> getLastPlugins(int limit) {\n        return getLatestPlugins(limit);\n    }\n\n    /**\n     * 获取最近的插件列表数据\n     *\n     * @param limit 获取的数据数量\n     * @return 插件列表数据\n     */\n    public List<InstalledPlugin> getLatestPlugins(int limit) {\n        SQLiteDatabase db = mDBHelper.getReadableDatabase();\n        // 查询所有type为uuid的行，按自增ID主键倒序\n        // 即自增ID越大的uuid为最新安装的\n        Cursor cursor = db.query(\n                InstalledPluginDBHelper.TABLE_NAME_MANAGER,\n                new String[]{InstalledPluginDBHelper.COLUMN_UUID},\n                InstalledPluginDBHelper.COLUMN_TYPE + \" = ?\",\n                new String[]{String.valueOf(InstalledType.TYPE_UUID)},\n                null,\n                null,\n                InstalledPluginDBHelper.COLUMN_ID + \" DESC\",\n                Integer.toString(limit)\n        );\n        List<String> uuids = new ArrayList<>();\n        while (cursor.moveToNext()) {\n            String uuid = cursor.getString(cursor.getColumnIndex(InstalledPluginDBHelper.COLUMN_UUID));\n            uuids.add(uuid);\n        }\n        cursor.close();\n        List<InstalledPlugin> installedPlugins = new ArrayList<>();\n        for (String uuid : uuids) {\n            installedPlugins.add(getInstalledPluginByUUID(uuid));\n        }\n        return installedPlugins;\n    }\n\n    private List<ContentValues> parseConfig(PluginConfig pluginConfig,\n                                            Map<String, String> soDirMap,// key:type+partKey\n                                            String oDexDir\n    ) {\n        List<InstalledRow> installedRows = new ArrayList<>();\n        if (pluginConfig.pluginLoader != null) {\n            String soDir = soDirMap.get(Integer.toString(InstalledType.TYPE_PLUGIN_LOADER) + null);\n            installedRows.add(new InstalledRow(pluginConfig.pluginLoader.hash, null, pluginConfig.pluginLoader.file.getAbsolutePath(), InstalledType.TYPE_PLUGIN_LOADER,\n                    soDir, oDexDir));\n        }\n        if (pluginConfig.runTime != null) {\n            String soDir = soDirMap.get(Integer.toString(InstalledType.TYPE_PLUGIN_RUNTIME) + null);\n            installedRows.add(new InstalledRow(pluginConfig.runTime.hash, null, pluginConfig.runTime.file.getAbsolutePath(), InstalledType.TYPE_PLUGIN_RUNTIME,\n                    soDir, oDexDir));\n        }\n        if (pluginConfig.plugins != null) {\n            Set<Map.Entry<String, PluginConfig.PluginFileInfo>> plugins = pluginConfig.plugins.entrySet();\n            for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : plugins) {\n                PluginConfig.PluginFileInfo fileInfo = plugin.getValue();\n                String partKey = plugin.getKey();\n                String soDir = soDirMap.get(InstalledType.TYPE_PLUGIN + partKey);\n                installedRows.add(\n                        new InstalledRow(\n                                fileInfo.hash,\n                                fileInfo.businessName,\n                                partKey,\n                                fileInfo.dependsOn,\n                                fileInfo.file.getAbsolutePath(),\n                                InstalledType.TYPE_PLUGIN,\n                                fileInfo.hostWhiteList,\n                                soDir, oDexDir\n                        )\n                );\n            }\n        }\n        InstalledRow uuidRow = new InstalledRow();\n        uuidRow.type = InstalledType.TYPE_UUID;\n        uuidRow.filePath = pluginConfig.UUID;\n        installedRows.add(uuidRow);\n        List<ContentValues> contentValues = new ArrayList<>();\n        for (InstalledRow row : installedRows) {\n            row.installedTime = pluginConfig.storageDir.lastModified();\n            row.UUID = pluginConfig.UUID;\n            row.version = pluginConfig.UUID_NickName;\n            contentValues.add(row.toContentValues());\n        }\n        return contentValues;\n    }\n\n\n    /**\n     * 释放资源\n     */\n    public void close() {\n        mDBHelper.close();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstalledPlugin.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\n\nimport java.io.File;\nimport java.io.Serializable;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 已安装好的插件.\n * <p>\n * 这是一个Serializable类，目的是可以将这个类的对象放在Intent中跨进程传递。\n * 注意：equals()方法必须重载，并包含全部域变量。\n *\n * @author owenguo\n */\npublic class InstalledPlugin implements Serializable {\n\n    /**\n     * 标识一次插件发布的id\n     */\n    public String UUID;\n    /**\n     * 标识一次插件发布的id，可以使用自定义格式描述版本信息\n     */\n    public String UUID_NickName;\n\n    /**\n     * pluginLoader文件\n     */\n    public Part pluginLoaderFile;\n\n    /**\n     * runtime文件\n     */\n    public Part runtimeFile;\n    /**\n     * 插件文件\n     */\n    public Map<String, PluginPart> plugins = new HashMap<>();\n\n\n    InstalledPlugin() {\n    }\n\n\n    public boolean hasPart(String partKey) {\n        return plugins.containsKey(partKey);\n    }\n\n    public PluginPart getPlugin(String partKey) {\n        return plugins.get(partKey);\n    }\n\n    public Part getPart(String partKey) {\n        return plugins.get(partKey);\n    }\n\n    static public class Part implements Serializable {\n        final public int pluginType;\n        final public File pluginFile;\n        public File oDexDir;\n        public File libraryDir;\n\n        Part(int pluginType, File file, File oDexDir, File libraryDir) {\n            this.pluginType = pluginType;\n            this.oDexDir = oDexDir;\n            this.libraryDir = libraryDir;\n            this.pluginFile = file;\n        }\n    }\n\n    static public class PluginPart extends Part {\n        final public String businessName;\n        final public String[] dependsOn;\n        final public String[] hostWhiteList;\n\n        PluginPart(int pluginType, String businessName, File file, File oDexDir, File libraryDir, String[] dependsOn, String[] hostWhiteList) {\n            super(pluginType, file, oDexDir, libraryDir);\n            this.businessName = businessName;\n            this.dependsOn = dependsOn;\n            this.hostWhiteList = hostWhiteList;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstalledPluginDBHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class InstalledPluginDBHelper extends SQLiteOpenHelper {\n\n    /**\n     * 数据库名称\n     */\n    final static String DB_NAME_PREFIX = \"shadow_installed_plugin_db\";\n    /**\n     * 表名称\n     */\n    public final static String TABLE_NAME_MANAGER = \"shadowPluginManager\";\n\n    /**\n     * 自增主键\n     */\n    public final static String COLUMN_ID = \"id\";\n    /**\n     * 插件的hash\n     */\n    public final static String COLUMN_HASH = \"hash\";\n    /**\n     * 插件的类型\n     */\n    public final static String COLUMN_TYPE = \"type\";\n    /**\n     * 插件的路径\n     */\n    public final static String COLUMN_PATH = \"filePath\";\n    /**\n     * 插件的businessName\n     */\n    public final static String COLUMN_BUSINESS_NAME = \"businessName\";\n    /**\n     * 插件的名称\n     */\n    public final static String COLUMN_PARTKEY = \"partKey\";\n    /**\n     * 插件的依赖\n     */\n    public final static String COLUMN_DEPENDSON = \"dependsOn\";\n    /**\n     * 插件的uuid\n     */\n    public final static String COLUMN_UUID = \"uuid\";\n    /**\n     * 插件的版本号\n     */\n    public final static String COLUMN_VERSION = \"version\";\n    /**\n     * 插件的安装时间\n     */\n    public final static String COLUMN_INSTALL_TIME = \"installedTime\";\n    /**\n     * 插件的dex目录\n     */\n    public final static String COLUMN_PLUGIN_ODEX = \"odexPath\";\n    /**\n     * 插件的lib目录\n     */\n    public final static String COLUMN_PLUGIN_LIB = \"libPath\";\n    /**\n     * 插件的依赖\n     */\n    public final static String COLUMN_HOST_WHITELIST = \"hostWhiteList\";\n    /**\n     * 数据库的版本号\n     */\n    private final static int VERSION = 4;\n\n\n    public InstalledPluginDBHelper(Context context, String name) {\n        super(context, DB_NAME_PREFIX + name, null, VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        String sql = \"CREATE TABLE IF NOT EXISTS \" + TABLE_NAME_MANAGER + \" ( \"\n                + COLUMN_ID + \" INTEGER PRIMARY KEY AUTOINCREMENT,\"\n                + COLUMN_HASH + \" VARCHAR , \"\n                + COLUMN_PATH + \" VARCHAR, \"\n                + COLUMN_TYPE + \" INTEGER, \"\n                + COLUMN_BUSINESS_NAME + \" VARCHAR, \"\n                + COLUMN_PARTKEY + \" VARCHAR, \"\n                + COLUMN_DEPENDSON + \" VARCHAR, \"\n                + COLUMN_UUID + \" VARCHAR, \"\n                + COLUMN_VERSION + \" VARCHAR, \"\n                + COLUMN_INSTALL_TIME + \" INTEGER ,\"\n                + COLUMN_PLUGIN_ODEX + \" VARCHAR ,\"\n                + COLUMN_PLUGIN_LIB + \" VARCHAR ,\"\n                + COLUMN_HOST_WHITELIST + \" VARCHAR \"\n                + \");\";\n        db.execSQL(sql);\n    }\n\n    @Override\n    @SuppressLint(\"Range\")\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        if (oldVersion < 2) {\n            db.beginTransaction();\n            try {\n                Cursor cursor = db.query(\n                        true,\n                        TABLE_NAME_MANAGER,\n                        new String[]{COLUMN_UUID, COLUMN_TYPE},\n                        COLUMN_TYPE + \" = ?\",\n                        new String[]{\"2\"},//Interface Type\n                        null, null, null, null\n                );\n                List<String> uuids = new ArrayList<>();\n                while (cursor.moveToNext()) {\n                    String uuid = cursor.getString(cursor.getColumnIndex(COLUMN_UUID));\n                    uuids.add(uuid);\n                }\n                cursor.close();\n\n                for (String uuid : uuids) {\n                    db.delete(TABLE_NAME_MANAGER, COLUMN_UUID + \" = ?\", new String[]{uuid});\n                }\n\n                db.setTransactionSuccessful();\n            } finally {\n                db.endTransaction();\n            }\n        }\n        if (oldVersion < 3) {\n            db.beginTransaction();\n            try {\n                //添加列COLUMN_HOST_WHITELIST\n                db.execSQL(\"ALTER TABLE \" + TABLE_NAME_MANAGER + \" ADD \" + COLUMN_HOST_WHITELIST + \" VARCHAR\");\n\n                db.setTransactionSuccessful();\n            } finally {\n                db.endTransaction();\n            }\n\n        }\n        if (oldVersion < 4) {\n            db.beginTransaction();\n            try {\n                //添加列COLUMN_BUSINESS_NAME。所有旧行保持空值即可，表示同宿主相同业务。\n                db.execSQL(\"ALTER TABLE \" + TABLE_NAME_MANAGER + \" ADD \" + COLUMN_BUSINESS_NAME + \" VARCHAR\");\n\n                db.setTransactionSuccessful();\n            } finally {\n                db.endTransaction();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstalledRow.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport android.content.ContentValues;\n\nimport org.json.JSONArray;\n\nimport java.util.Arrays;\n\npublic class InstalledRow {\n\n    public String hash;\n\n    public long installedTime;\n\n    public String partKey;\n\n    public String businessName;\n\n    public String[] dependsOn;\n\n    public String[] hostWhiteList;\n\n    public String filePath;\n\n    public int type;\n\n    public String UUID;\n\n    public String version;\n\n    public String soDir;\n\n    public String oDexDir;\n\n    public InstalledRow() {\n    }\n\n    public InstalledRow(String hash, String partKey, String filePath, int type, String soDir, String oDexDir) {\n        this.hash = hash;\n        this.partKey = partKey;\n        this.filePath = filePath;\n        this.type = type;\n        this.soDir = soDir;\n        this.oDexDir = oDexDir;\n    }\n\n    public InstalledRow(String hash, String businessName, String partKey, String[] dependsOn, String filePath, int type, String[] hostWhiteList, String soDir, String oDexDir) {\n        this(hash, partKey, filePath, type, soDir, oDexDir);\n        this.businessName = businessName;\n        this.dependsOn = dependsOn;\n        this.hostWhiteList = hostWhiteList;\n    }\n\n    public ContentValues toContentValues() {\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(InstalledPluginDBHelper.COLUMN_HASH, hash);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_INSTALL_TIME, installedTime);\n        if (businessName != null) {\n            contentValues.put(InstalledPluginDBHelper.COLUMN_BUSINESS_NAME, businessName);\n        }\n        if (partKey != null) {\n            contentValues.put(InstalledPluginDBHelper.COLUMN_PARTKEY, partKey);\n        }\n        if (dependsOn != null) {\n            JSONArray jsonArray = new JSONArray(Arrays.asList(dependsOn));\n            contentValues.put(InstalledPluginDBHelper.COLUMN_DEPENDSON, jsonArray.toString());\n        }\n        if (hostWhiteList != null) {\n            JSONArray jsonArray = new JSONArray(Arrays.asList(hostWhiteList));\n            contentValues.put(InstalledPluginDBHelper.COLUMN_HOST_WHITELIST, jsonArray.toString());\n        }\n        contentValues.put(InstalledPluginDBHelper.COLUMN_TYPE, type);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_UUID, UUID);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_VERSION, version);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_PATH, filePath);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_PLUGIN_LIB, soDir);\n        contentValues.put(InstalledPluginDBHelper.COLUMN_PLUGIN_ODEX, oDexDir);\n        return contentValues;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/InstalledType.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\npublic class InstalledType {\n\n    public final static int TYPE_PLUGIN = 1;\n\n    public final static int TYPE_PLUGIN_LOADER = 3;\n\n    public final static int TYPE_PLUGIN_RUNTIME = 4;\n\n    public final static int TYPE_UUID = 5;\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/MinFileUtils.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * 复制自\n *\n * @version $Id: FileUtils.java 1722481 2016-01-01 01:42:04Z dbrosius $\n * <p>\n * 没有使用完整的commons-io是因为要控制方法数\n */\npublic class MinFileUtils {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(MinFileUtils.class);\n\n    /**\n     * 保证文件的父目录存在，如果不存在，则从不存在的祖先目录开始创建完成路径\n     *\n     * @param file 需要\n     * @throws IOException\n     */\n    public static void ensureParentDirExists(File file) throws IOException {\n        File parent = file.getParentFile();\n        if (!parent.isDirectory() && !parent.mkdirs()) {\n            throw new IOException(\"创建父目录失败,文件目录:\" + file.getAbsolutePath() + \" parent dir exists=\" + parent.exists());\n        }\n    }\n\n    /**\n     * Cleans a directory without deleting it.\n     *\n     * @param directory directory to clean\n     * @throws IOException              in case cleaning is unsuccessful\n     * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory\n     */\n    public static void cleanDirectory(final File directory) throws IOException {\n        final File[] files = verifiedListFiles(directory);\n\n        IOException exception = null;\n        for (final File file : files) {\n            try {\n                forceDelete(file);\n            } catch (final IOException ioe) {\n                exception = ioe;\n            }\n        }\n\n        if (null != exception) {\n            throw exception;\n        }\n    }\n\n    /**\n     * Deletes a file. If file is a directory, delete it and all sub-directories.\n     * <p>\n     * The difference between File.delete() and this method are:\n     * <ul>\n     * <li>A directory to be deleted does not have to be empty.</li>\n     * <li>You get exceptions when a file or directory cannot be deleted.\n     * (java.io.File methods returns a boolean)</li>\n     * </ul>\n     *\n     * @param file file or directory to delete, must not be {@code null}\n     * @throws NullPointerException  if the directory is {@code null}\n     * @throws FileNotFoundException if the file was not found\n     * @throws IOException           in case deletion is unsuccessful\n     */\n    private static void forceDelete(final File file) throws IOException {\n        if (file.isDirectory()) {\n            deleteDirectory(file);\n        } else {\n            final boolean filePresent = file.exists();\n            if (!file.delete()) {\n                if (!filePresent) {\n                    throw new FileNotFoundException(\"File does not exist: \" + file);\n                }\n                final String message =\n                        \"Unable to delete file: \" + file;\n                throw new IOException(message);\n            }\n        }\n    }\n\n    /**\n     * Deletes a directory recursively.\n     *\n     * @param directory directory to delete\n     * @throws IOException              in case deletion is unsuccessful\n     * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory\n     */\n    private static void deleteDirectory(final File directory) throws IOException {\n        if (!directory.exists()) {\n            return;\n        }\n\n        cleanDirectory(directory);\n\n        if (!directory.delete()) {\n            final String message =\n                    \"Unable to delete directory \" + directory + \".\";\n            throw new IOException(message);\n        }\n    }\n\n    /**\n     * Lists files in a directory, asserting that the supplied directory satisfies exists and is a directory\n     *\n     * @param directory The directory to list\n     * @return The files in the directory, never null.\n     * @throws IOException if an I/O error occurs\n     */\n    private static File[] verifiedListFiles(File directory) throws IOException {\n        if (!directory.exists()) {\n            final String message = directory + \" does not exist\";\n            throw new IllegalArgumentException(message);\n        }\n\n        if (!directory.isDirectory()) {\n            final String message = directory + \" is not a directory\";\n            throw new IllegalArgumentException(message);\n        }\n\n        final File[] files = directory.listFiles();\n        if (files == null) {  // null if security restricted\n            throw new IOException(\"Failed to list contents of \" + directory);\n        }\n        return files;\n    }\n\n    public static void writeOutZipEntry(ZipFile zipFile, ZipEntry entry,\n                                        File outputDir, String outputFileName) throws IOException {\n        InputStream inputStream = null;\n        try {\n            inputStream = zipFile.getInputStream(entry);\n            writeOutInputStream(outputDir, outputFileName, inputStream);\n        } finally {\n            if (inputStream != null) {\n                inputStream.close();\n            }\n        }\n    }\n\n    public static void writeOutInputStream(File outputDir, String outputFileName,\n                                           InputStream inputStream) throws IOException {\n        BufferedOutputStream output = null;\n        try {\n            File file = new File(outputDir, outputFileName);\n            output = new BufferedOutputStream(\n                    new FileOutputStream(file));\n            BufferedInputStream input = new BufferedInputStream(inputStream);\n            byte b[] = new byte[8192];\n            int n;\n            while ((n = input.read(b, 0, 8192)) >= 0) {\n                output.write(b, 0, n);\n            }\n        } finally {\n            if (output != null) {\n                output.close();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/ODexBloc.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport android.os.Build;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport dalvik.system.DexClassLoader;\n\npublic class ODexBloc {\n\n    private static ConcurrentHashMap<String, Object> sLocks = new ConcurrentHashMap<>();\n\n    /**\n     * DexClassLoader的optimizedDirectory参数从API 27起就无效了\n     * 此方法统一判断这一特性是否生效\n     *\n     * @return <code>true</code>表示ODexBloc还有作用\n     */\n    public static boolean isEffective() {\n        return Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1;\n    }\n\n    public static void oDexPlugin(File apkFile, File oDexDir, File copiedTagFile) throws InstallPluginException {\n        if (!isEffective()) {\n            return;\n        }\n\n        String key = apkFile.getAbsolutePath();\n        Object lock = sLocks.get(key);\n        if (lock == null) {\n            lock = new Object();\n            sLocks.put(key, lock);\n        }\n\n\n        synchronized (lock) {\n            if (copiedTagFile.exists()) {\n                return;\n            }\n\n            //如果odex目录存在但是个文件，不是目录，那超出预料了。删除了也不一定能工作正常。\n            if (oDexDir.exists() && oDexDir.isFile()) {\n                throw new InstallPluginException(\"oDexDir=\" + oDexDir.getAbsolutePath() + \"已存在，但它是个文件，不敢贸然删除\");\n            }\n            //创建oDex目录\n            oDexDir.mkdirs();\n\n            new DexClassLoader(apkFile.getAbsolutePath(), oDexDir.getAbsolutePath(), null, ODexBloc.class.getClassLoader());\n\n            try {\n                copiedTagFile.createNewFile();\n            } catch (IOException e) {\n                throw new InstallPluginException(\"oDexPlugin完毕 创建tag文件失败：\" + copiedTagFile.getAbsolutePath(), e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/PluginConfig.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class PluginConfig {\n\n    /**\n     * 配置json文件的格式版本号\n     */\n    public int version;\n    /**\n     * 配置json文件的格式兼容版本号\n     */\n    public int[] compact_version;\n    /**\n     * 标识一次插件发布的id\n     */\n    public String UUID;\n    /**\n     * 标识一次插件发布的id，可以使用自定义格式描述版本信息\n     */\n    public String UUID_NickName;\n    /**\n     * pluginLoaderAPk 文件信息\n     */\n    public FileInfo pluginLoader;\n    /**\n     * runtime 文件信息\n     */\n    public FileInfo runTime;\n    /**\n     * 业务插件 key: partKey value:文件信息\n     */\n    public Map<String, PluginFileInfo> plugins = new HashMap<>();\n    /**\n     * 插件的存储目录\n     */\n    public File storageDir;\n\n    public boolean isUnpacked() {\n        boolean pluginLoaderUnpacked = true;\n        if (pluginLoader != null) {\n            pluginLoaderUnpacked = pluginLoader.file.exists();\n        }\n\n        boolean runtimeUnpacked = true;\n        if (runTime != null) {\n            runtimeUnpacked = runTime.file.exists();\n        }\n\n        boolean pluginsUnpacked = true;\n        for (PluginFileInfo pluginFileInfo : plugins.values()) {\n            pluginsUnpacked = pluginsUnpacked && pluginFileInfo.file.exists();\n        }\n        return pluginLoaderUnpacked && runtimeUnpacked && pluginsUnpacked;\n    }\n\n    public static class FileInfo {\n        public final File file;\n        public final String hash;\n\n        FileInfo(File file, String hash) {\n            this.file = file;\n            this.hash = hash;\n        }\n    }\n\n    public static class PluginFileInfo extends FileInfo {\n        final String[] dependsOn;\n        final String[] hostWhiteList;\n        final String businessName;\n\n        PluginFileInfo(String businessName, FileInfo fileInfo, String[] dependsOn, String[] hostWhiteList) {\n            this(businessName, fileInfo.file, fileInfo.hash, dependsOn, hostWhiteList);\n        }\n\n        PluginFileInfo(String businessName, File file, String hash, String[] dependsOn, String[] hostWhiteList) {\n            super(file, hash);\n            this.businessName = businessName;\n            this.dependsOn = dependsOn;\n            this.hostWhiteList = hostWhiteList;\n        }\n    }\n\n    public static PluginConfig parseFromJson(JSONObject configJson, File storageDir) throws JSONException {\n        PluginConfig pluginConfig = new PluginConfig();\n        pluginConfig.version = configJson.getInt(\"version\");\n        JSONArray compact_version_json = configJson.optJSONArray(\"compact_version\");\n        if (compact_version_json != null && compact_version_json.length() > 0) {\n            pluginConfig.compact_version = new int[compact_version_json.length()];\n            for (int i = 0; i < compact_version_json.length(); i++) {\n                pluginConfig.compact_version[i] = compact_version_json.getInt(i);\n            }\n        }\n        //todo #27 json的版本检查和不兼容检查\n        pluginConfig.UUID = configJson.getString(\"UUID\");\n        pluginConfig.UUID_NickName = configJson.getString(\"UUID_NickName\");\n\n        JSONObject loaderJson = configJson.optJSONObject(\"pluginLoader\");\n        if (loaderJson != null) {\n            pluginConfig.pluginLoader = getFileInfo(loaderJson, storageDir);\n        }\n\n        JSONObject runtimeJson = configJson.optJSONObject(\"runtime\");\n        if (runtimeJson != null) {\n            pluginConfig.runTime = getFileInfo(runtimeJson, storageDir);\n        }\n\n        JSONArray pluginArray = configJson.optJSONArray(\"plugins\");\n        if (pluginArray != null && pluginArray.length() > 0) {\n            for (int i = 0; i < pluginArray.length(); i++) {\n                JSONObject plugin = pluginArray.getJSONObject(i);\n                String partKey = plugin.getString(\"partKey\");\n                pluginConfig.plugins.put(partKey, getPluginFileInfo(plugin, storageDir));\n            }\n        }\n\n        pluginConfig.storageDir = storageDir;\n        return pluginConfig;\n    }\n\n    private static FileInfo getFileInfo(JSONObject jsonObject, File storageDir) throws JSONException {\n        String name = jsonObject.getString(\"apkName\");\n        String hash = jsonObject.getString(\"hash\");\n        return new FileInfo(new File(storageDir, name), hash);\n    }\n\n    private static PluginFileInfo getPluginFileInfo(JSONObject jsonObject, File storageDir) throws JSONException {\n        String businessName = jsonObject.optString(\"businessName\", \"\");\n        FileInfo fileInfo = getFileInfo(jsonObject, storageDir);\n        String[] dependsOn = getArrayStringByName(jsonObject, \"dependsOn\");\n        String[] hostWhiteList = getArrayStringByName(jsonObject, \"hostWhiteList\");\n        return new PluginFileInfo(businessName, fileInfo, dependsOn, hostWhiteList);\n    }\n\n    private static String[] getArrayStringByName(JSONObject jsonObject, String name) throws JSONException {\n        JSONArray jsonArray = jsonObject.optJSONArray(name);\n        String[] dependsOn;\n        if (jsonArray != null) {\n            dependsOn = new String[jsonArray.length()];\n            for (int i = 0; i < jsonArray.length(); i++) {\n                dependsOn[i] = jsonArray.getString(i);\n            }\n        } else {\n            dependsOn = new String[]{};\n        }\n        return dependsOn;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/SafeZipFile.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Enumeration;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * 避免ZipperDown漏洞\n */\npublic class SafeZipFile extends ZipFile {\n\n    public SafeZipFile(File file) throws IOException {\n        super(file);\n    }\n\n    @Override\n    public Enumeration<? extends ZipEntry> entries() {\n        return new SafeZipEntryIterator(super.entries());\n    }\n\n    private static class SafeZipEntryIterator implements Enumeration<ZipEntry> {\n\n        final private Enumeration<? extends ZipEntry> delegate;\n\n        private SafeZipEntryIterator(Enumeration<? extends ZipEntry> delegate) {\n            this.delegate = delegate;\n        }\n\n        @Override\n        public boolean hasMoreElements() {\n            return delegate.hasMoreElements();\n        }\n\n        @Override\n        public ZipEntry nextElement() {\n            ZipEntry entry = delegate.nextElement();\n            if (null != entry) {\n                String name = entry.getName();\n                if (null != name && (name.contains(\"../\") || name.contains(\"..\\\\\"))) {\n                    throw new SecurityException(\"非法entry路径:\" + entry.getName());\n                }\n            }\n            return entry;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/main/java/com/tencent/shadow/core/manager/installplugin/UnpackManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport static com.tencent.shadow.core.utils.Md5.md5File;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.Enumeration;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n\npublic class UnpackManager {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(UnpackManager.class);\n\n    private static final String CONFIG_FILENAME = \"config.json\";//todo #28 json的格式需要沉淀文档。\n    private static final String DEFAULT_STORE_DIR_NAME = \"ShadowPluginManager\";\n\n    private final File mPluginUnpackedDir;\n\n    private final String mAppName;\n\n    public UnpackManager(File root, String appName) {\n        File parent = new File(root, DEFAULT_STORE_DIR_NAME);\n        mPluginUnpackedDir = new File(parent, \"UnpackedPlugin\");\n        mPluginUnpackedDir.mkdirs();\n        mAppName = appName;\n    }\n\n\n    File getVersionDir(String appHash) {\n        return AppCacheFolderManager.getVersionDir(mPluginUnpackedDir, mAppName, appHash);\n    }\n\n    public File getAppDir() {\n        return AppCacheFolderManager.getAppDir(mPluginUnpackedDir, mAppName);\n    }\n\n    /**\n     * 获取插件解包的目标目录。根据target的文件名决定。\n     *\n     * @param target Target\n     * @return 插件解包的目标目录\n     */\n    public File getPluginUnpackDir(String appHash, File target) {\n        return new File(getVersionDir(appHash), target.getName());\n    }\n\n    public String zipHash(File zip) {\n        return md5File(zip);\n    }\n\n    public JSONObject getConfigJson(File zip) {\n        ZipFile zipFile = null;\n        try {\n            zipFile = new SafeZipFile(zip);\n            ZipEntry entry = zipFile.getEntry(CONFIG_FILENAME);\n            InputStream inputStream = zipFile.getInputStream(entry);\n            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));\n            StringBuilder sb = new StringBuilder();\n            for (String line; (line = br.readLine()) != null; ) {\n                sb.append(line);\n            }\n\n            return new JSONObject(sb.toString());\n        } catch (JSONException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            try {\n                if (zipFile != null) {\n                    zipFile.close();\n                }\n            } catch (IOException e) {\n                mLogger.warn(\"zip关闭时出错忽略\", e);\n            }\n        }\n    }\n\n    /**\n     * 解包一个下载好的插件\n     *\n     * @param target          插件包\n     * @param pluginUnpackDir 解压目录\n     */\n    public void unpackPlugin(File target, File pluginUnpackDir) throws IOException {\n        pluginUnpackDir.mkdirs();\n        MinFileUtils.cleanDirectory(pluginUnpackDir);\n\n        ZipFile zipFile = null;\n        try {\n            zipFile = new SafeZipFile(target);\n            Enumeration<? extends ZipEntry> entries = zipFile.entries();\n            while (entries.hasMoreElements()) {\n                ZipEntry entry = entries.nextElement();\n                if (!entry.isDirectory()) {\n                    MinFileUtils.writeOutZipEntry(zipFile, entry, pluginUnpackDir, entry.getName());\n                }\n            }\n        } finally {\n            try {\n                if (zipFile != null) {\n                    zipFile.close();\n                }\n            } catch (IOException e) {\n                mLogger.warn(\"zip关闭时出错忽略\", e);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/manager/src/test/java/com/tencent/shadow/core/manager/installplugin/SafeZipFileTest.java",
    "content": "package com.tencent.shadow.core.manager.installplugin;\n\nimport com.tencent.shadow.core.utils.Md5;\n\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.Enumeration;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipOutputStream;\n\npublic class SafeZipFileTest {\n    private File testZipFile;\n    private ZipOutputStream zipOutputStream;\n\n    @Before\n    public void setUp() throws Exception {\n        testZipFile = File.createTempFile(\"SafeZipFileTest\", \".zip\");\n        zipOutputStream = new ZipOutputStream(new FileOutputStream(testZipFile));\n    }\n\n    @After\n    public void tearDown() throws Exception {\n        zipOutputStream.close();\n        boolean success = testZipFile.delete();\n        if (!success) {\n            throw new RuntimeException(\"删除临时文件失败\");\n        }\n    }\n\n    @Test\n    public void testContainsManifest() throws IOException {\n        //向测试zip中写入一个文件\n        ZipOutputStream out = this.zipOutputStream;\n        ZipEntry e = new ZipEntry(\"META-INF/MANIFEST.MF\");\n        out.putNextEntry(e);\n        byte[] data = \"测试内容\".getBytes();\n        out.write(data, 0, data.length);\n        out.closeEntry();\n        out.close();\n\n        //测试用SafeZipFile类型遍历zip文件\n        ZipFile zipFile\n                = new SafeZipFile(testZipFile);\n        Enumeration<? extends ZipEntry> entries = zipFile.entries();\n        if (entries.hasMoreElements()) {\n            ZipEntry entry = entries.nextElement();\n            String entryName = entry.getName();\n            Assert.assertTrue(entryName.length() > 0);\n        }\n        zipFile.close();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/.gitignore",
    "content": "/build\n*.iml\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    defaultConfig {\n        compileSdkVersion project.COMPILE_SDK_VERSION\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        testInstrumentationRunner \"com.tencent.shadow.core.pluginmanager.CustomAndroidJUnitRunner\"\n    }\n    testOptions {\n        animationsDisabled = true\n    }\n}\n\ndependencies {\n    testImplementation \"junit:junit:$junit_version\"\n    androidTestImplementation \"androidx.test:core:$androidx_test_version\"\n    androidTestImplementation \"androidx.test.ext:junit:$androidx_test_junit_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-core:$espresso_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-remote:$espresso_version\"\n    implementation \"androidx.test.espresso:espresso-idling-resource:$espresso_version\"\n    androidTestImplementation \"androidx.test:runner:$androidx_test_version\"\n\n    androidTestImplementation \"commons-io:commons-io:$commons_io_android_version\"\n    androidTestImplementation project(':common')\n\n    implementation project(':manager')\n}\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/java/com/tencent/shadow/core/manager/installplugin/DbCompatibilityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.manager.installplugin;\n\nimport static com.tencent.shadow.core.manager.installplugin.InstalledPluginDBHelper.DB_NAME_PREFIX;\nimport static com.tencent.shadow.core.manager_aar.test.R.raw;\n\nimport android.content.Context;\nimport android.os.Build;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.Assert;\nimport org.junit.Assume;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.nio.charset.Charset;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\n/**\n * 数据库兼容性测试\n * <p>\n * 这项测试在写测试用例前，先回滚代码到旧版本，利用正常的config.json(如res/raw/plugin1.json)通过旧版本的\n * InstalledDao安装，生成旧版本的数据库。然后用{@link DbCompatibilityTest#dumpDbToFile(File)}方法\n * 将该旧数据库导出成sql文件上库(如res/raw/init_sql_version1.sql)。\n * 注意：PRAGMA user_version不能dump出来，需要手工写。\n * 再准备一个升级过后数据库dump出来的sql（如res/raw/expect_sql_version1.sql)作为Assert expected。\n */\n@RunWith(AndroidJUnit4.class)\npublic class DbCompatibilityTest {\n    private static final String TEST_DB_NAME = \"DbCompatibilityTest\";\n\n    private File databasePath;\n    private Context context;\n\n    @Before\n    public void setUp() {\n        //因为sqlite3本身的bug和版本不一致导致dump结果不一致，这个单元测试只能在Android P上完整正常运行\n        //Android 4.4系统在init时会出现内部错误\n        //Android 7.0系统dump出的sql对表名加了额外的双引号，兼容略麻烦\n        Assume.assumeTrue(Build.VERSION.SDK_INT == Build.VERSION_CODES.P);\n\n        context = ApplicationProvider.getApplicationContext();\n        databasePath = context.getDatabasePath(\n                InstalledPluginDBHelper.DB_NAME_PREFIX + TEST_DB_NAME\n        );\n    }\n\n    @Test\n    public void testDbNamePrefixNotChanged() {\n        Assert.assertEquals(\"DB名前缀不能改\",\n                \"shadow_installed_plugin_db\",\n                DB_NAME_PREFIX\n        );\n    }\n\n    @Test\n    public void testCompatibleWithVersion1()\n            throws IOException, InterruptedException, TimeoutException {\n        testCompatibleWithVersion(\n                raw.init_sql_version1,\n                raw.expect_sql_version1\n        );\n    }\n\n    @Test\n    public void testCompatibleWithVersion2()\n            throws IOException, InterruptedException, TimeoutException {\n        testCompatibleWithVersion(\n                raw.init_sql_version2,\n                raw.expect_sql_version2\n        );\n    }\n\n    @Test\n    public void testCompatibleWithVersion3()\n            throws IOException, InterruptedException, TimeoutException {\n        testCompatibleWithVersion(\n                raw.init_sql_version3,\n                raw.expect_sql_version3\n        );\n    }\n\n    @Test\n    public void testCompatibleWithVersion4()\n            throws IOException, InterruptedException, TimeoutException {\n        testCompatibleWithVersion(\n                raw.init_sql_version4,\n                raw.expect_sql_version4\n        );\n    }\n\n    //    @Test //取消注释以生成文件\n    public void generateCurrentVersionInitSqlFile() throws Exception {\n        initDbWithConfigJson(raw.plugin1);\n    }\n\n    private void initDbWithConfigJson(int... configJsonResId)\n            throws IOException, JSONException, TimeoutException, InterruptedException {\n        List<String> configs = new LinkedList<>();\n        for (int resId : configJsonResId) {\n            File configJsonFile = getResRawFile(resId);\n            String configJson = FileUtils.readFileToString(configJsonFile, Charset.defaultCharset());\n            FileUtils.forceDelete(configJsonFile);\n            configs.add(configJson);\n        }\n\n\n        InstalledPluginDBHelper dbHelper = new InstalledPluginDBHelper(context, TEST_DB_NAME);\n        InstalledDao installedDao = new InstalledDao(dbHelper);\n\n        for (String configJson : configs) {\n            installedDao.insert(PluginConfig.parseFromJson(new JSONObject(configJson), context.getCacheDir()), null, null);\n        }\n\n        dbHelper.close();\n\n        File dumpSqlFile = File.createTempFile(\"initDbWithConfigJson\", \".sql\", context.getExternalCacheDir());\n        dumpDbToFile(dumpSqlFile);\n\n        Assert.assertTrue(dumpSqlFile.exists() && dumpSqlFile.length() > 0);\n\n        throw new RuntimeException(\"执行命令复制文件到电脑:adb pull \" + dumpSqlFile.getAbsolutePath());\n    }\n\n    private void testCompatibleWithVersion(int initSqlResId, int expectSqlResId)\n            throws IOException, InterruptedException, TimeoutException {\n        File initSqlFile = getResRawFile(initSqlResId);\n        initDb(initSqlFile);\n\n        InstalledPluginDBHelper dbHelper = new InstalledPluginDBHelper(context, TEST_DB_NAME);\n        dbHelper.getReadableDatabase();//只是为了触发onUpgrade方法\n        dbHelper.close();\n\n        File dumpSqlFile = File.createTempFile(\"dumpDb\", \".sql\");\n        dumpDbToFile(dumpSqlFile);\n\n        File exceptSqlFile = getResRawFile(expectSqlResId);\n\n        try {\n            Assert.assertEquals(\n                    FileUtils.readLines(exceptSqlFile, Charset.defaultCharset()),\n                    FileUtils.readLines(dumpSqlFile, Charset.defaultCharset())\n            );\n        } finally {\n            FileUtils.forceDelete(dumpSqlFile);\n            FileUtils.forceDelete(initSqlFile);\n            FileUtils.forceDelete(exceptSqlFile);\n            deleteDb();\n        }\n    }\n\n    private File getResRawFile(int resId) throws IOException {\n        InputStream is = context.getResources().openRawResource(resId);\n        File resRawFile = File.createTempFile(\"getResRawFile\", null);\n        IOUtils.copy(is, new FileOutputStream(resRawFile));\n        return resRawFile;\n    }\n\n    /**\n     * 初始化数据库\n     * 用于将数据库通过sql脚本直接初始化成旧版本数据库。\n     * 旧版本数据库的sql采用手工执行sqlite3的.dump命令生成的。\n     */\n    private void initDb(File initSqlFile)\n            throws IOException, InterruptedException, TimeoutException {\n        String[] cmd = {\n                \"sqlite3\",\n                \"-init\",\n                initSqlFile.getAbsolutePath(),\n                databasePath.getAbsolutePath(),\n                \".exit\"\n        };\n        Process p = Runtime.getRuntime().exec(cmd);\n\n        boolean timeout = !p.waitFor(10, TimeUnit.SECONDS);\n\n        if (timeout) {\n            throw new TimeoutException(\"exec超时\");\n        }\n        int exitValue = p.exitValue();\n        if (exitValue != 0) {\n            String errorOutput = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());\n            throw new Error(\"exitValue==\" + exitValue + \" errorOutput==\" + errorOutput);\n        }\n    }\n\n    /**\n     * 将当前数据库Dump成sql文件\n     */\n    private void dumpDbToFile(File dumpSqlFile)\n            throws InterruptedException, IOException, TimeoutException {\n        File tempDumpCmdArgsFile = File.createTempFile(\"dumpDb\", \".cmd\");\n\n        PrintWriter pw = new PrintWriter(new FileWriter(tempDumpCmdArgsFile));\n        pw.println(\".output \" + dumpSqlFile.getAbsolutePath());\n        pw.println(\".dump \" + InstalledPluginDBHelper.TABLE_NAME_MANAGER);\n        pw.println(\".exit\");\n        pw.flush();\n        pw.close();\n\n        String[] cmd = {\n                \"sqlite3\",\n                databasePath.getAbsolutePath(),\n                \".read \" + tempDumpCmdArgsFile.getAbsolutePath()\n        };\n\n        Process p = Runtime.getRuntime().exec(cmd, null, context.getCacheDir());\n        boolean timeout = !p.waitFor(10, TimeUnit.SECONDS);\n\n        FileUtils.forceDelete(tempDumpCmdArgsFile);\n\n        if (timeout) {\n            throw new TimeoutException(\"exec超时\");\n        }\n\n        int exitValue = p.exitValue();\n        if (exitValue != 0) {\n            String errorOutput = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());\n            throw new Error(\"exitValue==\" + exitValue + \" errorOutput==\" + errorOutput);\n        }\n\n        Assert.assertTrue(dumpSqlFile.exists() && dumpSqlFile.length() > 0);\n    }\n\n    /**\n     * 删除数据库。\n     * 应该在每一个测试用例最后调用此方法删除数据库，避免对下一个测试用例有影响。\n     */\n    private void deleteDb() throws IOException {\n        FileUtils.forceDelete(databasePath);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/java/com/tencent/shadow/core/pluginmanager/CustomAndroidJUnitRunner.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.pluginmanager;\n\nimport android.os.Bundle;\n\nimport androidx.test.runner.AndroidJUnitRunner;\n\npublic class CustomAndroidJUnitRunner extends AndroidJUnitRunner {\n    @Override\n    public void onCreate(Bundle arguments) {\n        //禁止Google收集数据，避免因访问不到在测试结束后等待40秒超时\n        arguments.putString(\"disableAnalytics\", Boolean.toString(true));\n        super.onCreate(arguments);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/java/common/AndroidLogLoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage common;\n\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AndroidLogLoggerFactory implements ILoggerFactory {\n\n    private static final int LOG_LEVEL_TRACE = 5;\n    private static final int LOG_LEVEL_DEBUG = 4;\n    private static final int LOG_LEVEL_INFO = 3;\n    private static final int LOG_LEVEL_WARN = 2;\n    private static final int LOG_LEVEL_ERROR = 1;\n\n    private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory();\n\n    public static ILoggerFactory getInstance() {\n        return sInstance;\n    }\n\n    final private ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();\n\n    public Logger getLogger(String name) {\n        Logger simpleLogger = loggerMap.get(name);\n        if (simpleLogger != null) {\n            return simpleLogger;\n        } else {\n            Logger newInstance = new IVLogger(name);\n            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);\n            return oldInstance == null ? newInstance : oldInstance;\n        }\n    }\n\n    class IVLogger implements Logger {\n        private String name;\n\n        IVLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        private void log(int level, String message, Throwable t) {\n            final String tag = String.valueOf(name);\n\n            switch (level) {\n                case LOG_LEVEL_TRACE:\n                case LOG_LEVEL_DEBUG:\n                    if (t == null)\n                        Log.d(tag, message);\n                    else\n                        Log.d(tag, message, t);\n                    break;\n                case LOG_LEVEL_INFO:\n                    if (t == null)\n                        Log.i(tag, message);\n                    else\n                        Log.i(tag, message, t);\n                    break;\n                case LOG_LEVEL_WARN:\n                    if (t == null)\n                        Log.w(tag, message);\n                    else\n                        Log.w(tag, message, t);\n                    break;\n                case LOG_LEVEL_ERROR:\n                    if (t == null)\n                        Log.e(tag, message);\n                    else\n                        Log.e(tag, message, t);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return true;\n        }\n\n        @Override\n        public void trace(String msg) {\n            log(LOG_LEVEL_TRACE, msg, null);\n        }\n\n        @Override\n        public void trace(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String msg, Throwable throwable) {\n            log(LOG_LEVEL_TRACE, msg, throwable);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return true;\n        }\n\n        @Override\n        public void debug(String msg) {\n            log(LOG_LEVEL_DEBUG, msg, null);\n        }\n\n        @Override\n        public void debug(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String msg, Throwable throwable) {\n            log(LOG_LEVEL_DEBUG, msg, throwable);\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return true;\n        }\n\n        @Override\n        public void info(String msg) {\n            log(LOG_LEVEL_INFO, msg, null);\n        }\n\n        @Override\n        public void info(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String msg, Throwable throwable) {\n            log(LOG_LEVEL_INFO, msg, throwable);\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return true;\n        }\n\n        @Override\n        public void warn(String msg) {\n            log(LOG_LEVEL_WARN, msg, null);\n        }\n\n        @Override\n        public void warn(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String msg, Throwable throwable) {\n            log(LOG_LEVEL_WARN, msg, throwable);\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return true;\n        }\n\n        @Override\n        public void error(String msg) {\n            log(LOG_LEVEL_ERROR, msg, null);\n        }\n\n        @Override\n        public void error(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String msg, Throwable throwable) {\n            log(LOG_LEVEL_ERROR, msg, throwable);\n        }\n    }\n}\n\nclass FormattingTuple {\n\n    static public FormattingTuple NULL = new FormattingTuple(null);\n\n    private String message;\n    private Throwable throwable;\n    private Object[] argArray;\n\n    public FormattingTuple(String message) {\n        this(message, null, null);\n    }\n\n    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {\n        this.message = message;\n        this.throwable = throwable;\n        this.argArray = argArray;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public Object[] getArgArray() {\n        return argArray;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n}\n\nfinal class MessageFormatter {\n    static final char DELIM_START = '{';\n    static final char DELIM_STOP = '}';\n    static final String DELIM_STR = \"{}\";\n    private static final char ESCAPE_CHAR = '\\\\';\n\n    /**\n     * Performs single argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi there.\".\n     * <p>\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg            The argument to be substituted in place of the formatting anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(String messagePattern, Object arg) {\n        return arrayFormat(messagePattern, new Object[]{arg});\n    }\n\n    /**\n     * Performs a two argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi Alice. My name is Bob.\".\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg1           The argument to be substituted in place of the first formatting\n     *                       anchor\n     * @param arg2           The argument to be substituted in place of the second formatting\n     *                       anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n        return arrayFormat(messagePattern, new Object[]{arg1, arg2});\n    }\n\n\n    static final Throwable getThrowableCandidate(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            return null;\n        }\n\n        final Object lastEntry = argArray[argArray.length - 1];\n        if (lastEntry instanceof Throwable) {\n            return (Throwable) lastEntry;\n        }\n        return null;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n        Throwable throwableCandidate = getThrowableCandidate(argArray);\n        Object[] args = argArray;\n        if (throwableCandidate != null) {\n            args = trimmedCopy(argArray);\n        }\n        return arrayFormat(messagePattern, args, throwableCandidate);\n    }\n\n    private static Object[] trimmedCopy(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            throw new IllegalStateException(\"non-sensical empty or null argument array\");\n        }\n        final int trimemdLen = argArray.length - 1;\n        Object[] trimmed = new Object[trimemdLen];\n        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);\n        return trimmed;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {\n\n        if (messagePattern == null) {\n            return new FormattingTuple(null, argArray, throwable);\n        }\n\n        if (argArray == null) {\n            return new FormattingTuple(messagePattern);\n        }\n\n        int i = 0;\n        int j;\n        // use string builder for better multicore performance\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n\n        int L;\n        for (L = 0; L < argArray.length; L++) {\n\n            j = messagePattern.indexOf(DELIM_STR, i);\n\n            if (j == -1) {\n                // no more variables\n                if (i == 0) { // this is a simple string\n                    return new FormattingTuple(messagePattern, argArray, throwable);\n                } else { // add the tail string which contains no variables and return\n                    // the result.\n                    sbuf.append(messagePattern, i, messagePattern.length());\n                    return new FormattingTuple(sbuf.toString(), argArray, throwable);\n                }\n            } else {\n                if (isEscapedDelimeter(messagePattern, j)) {\n                    if (!isDoubleEscaped(messagePattern, j)) {\n                        L--; // DELIM_START was escaped, thus should not be incremented\n                        sbuf.append(messagePattern, i, j - 1);\n                        sbuf.append(DELIM_START);\n                        i = j + 1;\n                    } else {\n                        // The escape character preceding the delimiter start is\n                        // itself escaped: \"abc x:\\\\{}\"\n                        // we have to consume one backward slash\n                        sbuf.append(messagePattern, i, j - 1);\n                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                        i = j + 2;\n                    }\n                } else {\n                    // normal case\n                    sbuf.append(messagePattern, i, j);\n                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                    i = j + 2;\n                }\n            }\n        }\n        // append the characters following the last {} pair.\n        sbuf.append(messagePattern, i, messagePattern.length());\n        return new FormattingTuple(sbuf.toString(), argArray, throwable);\n    }\n\n    final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {\n\n        if (delimeterStartIndex == 0) {\n            return false;\n        }\n        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);\n        if (potentialEscape == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n        if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // special treatment of array values was suggested by 'lizongbo'\n    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {\n        if (o == null) {\n            sbuf.append(\"null\");\n            return;\n        }\n        if (!o.getClass().isArray()) {\n            safeObjectAppend(sbuf, o);\n        } else {\n            // check for primitive array types because they\n            // unfortunately cannot be cast to Object[]\n            if (o instanceof boolean[]) {\n                booleanArrayAppend(sbuf, (boolean[]) o);\n            } else if (o instanceof byte[]) {\n                byteArrayAppend(sbuf, (byte[]) o);\n            } else if (o instanceof char[]) {\n                charArrayAppend(sbuf, (char[]) o);\n            } else if (o instanceof short[]) {\n                shortArrayAppend(sbuf, (short[]) o);\n            } else if (o instanceof int[]) {\n                intArrayAppend(sbuf, (int[]) o);\n            } else if (o instanceof long[]) {\n                longArrayAppend(sbuf, (long[]) o);\n            } else if (o instanceof float[]) {\n                floatArrayAppend(sbuf, (float[]) o);\n            } else if (o instanceof double[]) {\n                doubleArrayAppend(sbuf, (double[]) o);\n            } else {\n                objectArrayAppend(sbuf, (Object[]) o, seenMap);\n            }\n        }\n    }\n\n    private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n        try {\n            String oAsString = o.toString();\n            sbuf.append(oAsString);\n        } catch (Throwable t) {\n            sbuf.append(\"[FAILED toString()]\");\n        }\n\n    }\n\n    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n        sbuf.append('[');\n        if (!seenMap.containsKey(a)) {\n            seenMap.put(a, null);\n            final int len = a.length;\n            for (int i = 0; i < len; i++) {\n                deeplyAppendParameter(sbuf, a[i], seenMap);\n                if (i != len - 1)\n                    sbuf.append(\", \");\n            }\n            // allow repeats in siblings\n            seenMap.remove(a);\n        } else {\n            sbuf.append(\"...\");\n        }\n        sbuf.append(']');\n    }\n\n    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/expect_sql_version1.sql",
    "content": "PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR , hostWhiteList VARCHAR, businessName VARCHAR);\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/expect_sql_version2.sql",
    "content": "PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR , hostWhiteList VARCHAR, businessName VARCHAR);\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[depends1]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/expect_sql_version3.sql",
    "content": "PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR , hostWhiteList VARCHAR, businessName VARCHAR);\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[depends1]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,'[host1]',NULL);\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL,NULL);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/expect_sql_version4.sql",
    "content": "PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, businessName VARCHAR, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR ,hostWhiteList VARCHAR );\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'businesstest','test_main','[]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,'[]');\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/init_sql_version1.sql",
    "content": "/*\n * version:1的数据库\n * 行1-4是没有interface的插件\n * 行5-9是有interface的插件\n*/\nPRAGMA foreign_keys=OFF;\nPRAGMA user_version = 1;\nBEGIN TRANSACTION;\nCREATE TABLE android_metadata (locale TEXT);\nINSERT INTO android_metadata VALUES('en_US');\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR );\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(5,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'1087DACB-3373-4E11-B50B-1B3076BD4F17','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(6,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'1087DACB-3373-4E11-B50B-1B3076BD4F17','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(7,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[]','1087DACB-3373-4E11-B50B-1B3076BD4F17','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(8,'test_interface_fake_hash','/data/user/0/com.tencent.shadow.core.manager.test/cache/test_interface.apk',2,'test_interface','[]','1087DACB-3373-4E11-B50B-1B3076BD4F17','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(9,NULL,'1087DACB-3373-4E11-B50B-1B3076BD4F17',5,NULL,NULL,'1087DACB-3373-4E11-B50B-1B3076BD4F17','plugin_config_version1.json',1556258429000,NULL,NULL);\nDELETE FROM sqlite_sequence;\nINSERT INTO sqlite_sequence VALUES('shadowPluginManager',9);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/init_sql_version2.sql",
    "content": "/*\n * version:2的数据库\n * 行1-4是一个插件\n*/\nPRAGMA foreign_keys=OFF;\nPRAGMA user_version = 2;\nBEGIN TRANSACTION;\nCREATE TABLE android_metadata (locale TEXT);\nINSERT INTO android_metadata VALUES('en_US');\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR );\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[depends1]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL);\nDELETE FROM sqlite_sequence;\nINSERT INTO sqlite_sequence VALUES('shadowPluginManager',4);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/init_sql_version3.sql",
    "content": "/*\n * version:3的数据库\n * 行1-4是一个插件\n*/\nPRAGMA foreign_keys=OFF;\nPRAGMA user_version = 3;\nBEGIN TRANSACTION;\nCREATE TABLE android_metadata (locale TEXT);\nINSERT INTO android_metadata VALUES('en_US');\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR , hostWhiteList VARCHAR);\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'test_main','[depends1]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,'[host1]');\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556258429000,NULL,NULL,NULL);\nDELETE FROM sqlite_sequence;\nINSERT INTO sqlite_sequence VALUES('shadowPluginManager',4);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/init_sql_version4.sql",
    "content": "/*\n * version:4的数据库\n * 行1-4是一个插件\n*/\nPRAGMA foreign_keys=OFF;\nPRAGMA user_version = 4;\nBEGIN TRANSACTION;\nCREATE TABLE shadowPluginManager ( id INTEGER PRIMARY KEY AUTOINCREMENT,hash VARCHAR , filePath VARCHAR, type INTEGER, businessName VARCHAR, partKey VARCHAR, dependsOn VARCHAR, uuid VARCHAR, version VARCHAR, installedTime INTEGER ,odexPath VARCHAR ,libPath VARCHAR ,hostWhiteList VARCHAR );\nINSERT INTO shadowPluginManager VALUES(1,'B0358A1919582A0FD467A42EEC40A5B7','/data/user/0/com.tencent.shadow.core.manager.test/cache/loader-apk-debug.apk',3,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(2,'51FDE1246F62D17D881493B037795D63','/data/user/0/com.tencent.shadow.core.manager.test/cache/runtime-apk-debug.apk',4,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nINSERT INTO shadowPluginManager VALUES(3,'DAC0234D1BE7F363A66263A01595DA64','/data/user/0/com.tencent.shadow.core.manager.test/cache/test-plugin-debug.apk',1,'businesstest','test_main','[]','0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,'[]');\nINSERT INTO shadowPluginManager VALUES(4,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16',5,NULL,NULL,NULL,'0087DACB-3373-4E11-B50B-1B3076BD4F16','plugin_config_version1.json',1556448760000,NULL,NULL,NULL);\nCOMMIT;\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/androidTest/res/raw/plugin1.json",
    "content": "{\n  \"compact_version\": [\n    1\n  ],\n  \"pluginLoader\": {\n    \"apkName\": \"loader-apk-debug.apk\",\n    \"hash\": \"B0358A1919582A0FD467A42EEC40A5B7\"\n  },\n  \"plugins\": [\n    {\n      \"partKey\": \"test_main\",\n      \"apkName\": \"test-plugin-debug.apk\",\n      \"hash\": \"DAC0234D1BE7F363A66263A01595DA64\"\n    }\n  ],\n  \"runtime\": {\n    \"apkName\": \"runtime-apk-debug.apk\",\n    \"hash\": \"51FDE1246F62D17D881493B037795D63\"\n  },\n  \"UUID\": \"0087DACB-3373-4E11-B50B-1B3076BD4F16\",\n  \"version\": 1,\n  \"UUID_NickName\": \"plugin_config_version1.json\"\n}\n"
  },
  {
    "path": "projects/sdk/core/manager-db-test/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.core.manager_aar\" />\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/build.gradle",
    "content": "apply plugin: 'kotlin'\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"com.squareup:javapoet:$javapoet_version\"\n    implementation project(':runtime')\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation \"commons-io:commons-io:$commons_io_jvm_version\"\n    testImplementation 'com.tencent.shadow.coding:java-build-config'\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n        apiVersion = \"1.3\"// 兼容低版本Gradle和https://youtrack.jetbrains.com/issue/KT-39389\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestKeys.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nsealed class AndroidManifestKeys {\n    companion object {\n        const val appComponentFactory = \"android:appComponentFactory\"\n        const val `package` = \"package\"\n        const val name = \"android:name\"\n        const val theme = \"android:theme\"\n        const val configChanges = \"android:configChanges\"\n        const val screenOrientation = \"android:screenOrientation\"\n        const val windowSoftInputMode = \"android:windowSoftInputMode\"\n        const val authorities = \"android:authorities\"\n        const val `intent-filter` = \"intent-filter\"\n        const val action = \"action\"\n        const val manifest = \"manifest\"\n        const val application = \"application\"\n        const val activity = \"activity\"\n        const val service = \"service\"\n        const val provider = \"provider\"\n        const val receiver = \"receiver\"\n        const val grantUriPermissions = \"android:grantUriPermissions\"\n    }\n}\ntypealias ComponentMapKey = String\ntypealias ComponentMapValue = Any\ntypealias ComponentMap = Map<ComponentMapKey, ComponentMapValue>\ntypealias MutableComponentMap = MutableMap<ComponentMapKey, ComponentMapValue>\ntypealias ManifestValueParser = (String) -> String"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReader.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nimport org.w3c.dom.Document\nimport org.w3c.dom.Element\nimport org.w3c.dom.Node\nimport java.io.File\nimport javax.xml.parsers.DocumentBuilderFactory\n\ntypealias ManifestMap = Map<String, Any>\n\n/**\n * 读取xml格式的Manifest到内存Map中\n */\nclass AndroidManifestReader {\n    /**\n     * 读取入口方法\n     *\n     * @param xmlFile com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件，\n     *                一般位于apk工程的build/intermediates/merged_manifest目录中。\n     */\n    fun read(xmlFile: File): ManifestMap {\n        val manifest = readXml(xmlFile).documentElement\n        val application = readApplication(manifest)\n        val globalAttributes = readGlobalAttributes(manifest, application)\n        val components = readComponents(application)\n        return globalAttributes.plus(components)\n    }\n\n    private fun readXml(xmlFile: File): Document {\n        try {\n            val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance()\n            val documentBuilder = documentBuilderFactory.newDocumentBuilder()\n            return documentBuilder.parse(xmlFile)!!\n        } catch (e: Exception) {\n            throw RuntimeException(\"xml应该是AGP生成的合法文件，所以不兼容任何xml读取错误\", e)\n        }\n    }\n\n    private fun readApplication(manifest: Element): Element? {\n        val elements = manifest.getElementsByTagName(AndroidManifestKeys.application)\n        return if (elements.length == 1) {\n            val node = elements.item(0)\n            assert(node.nodeType == Node.ELEMENT_NODE)\n            elements.item(0) as Element\n        } else {\n            null\n        }\n    }\n\n    /**\n     * 读取Manifest中那些唯一的属性\n     */\n    private fun readGlobalAttributes(manifest: Element, application: Element?): Map<String, Any> {\n        val globalAttributes = mutableMapOf<String, Any>()\n\n        fun manifestAttribute(name: String) {\n            globalAttributes[name] = manifest.getAttribute(name)\n        }\n\n        fun applicationAttribute(name: String) {\n            if (application != null) {\n                val attribute = application.getAttribute(name)\n                if (attribute.isNotEmpty()) {\n                    globalAttributes[name] = attribute\n                }\n            }\n        }\n\n        manifestAttribute(AndroidManifestKeys.`package`)\n        listOf(\n            AndroidManifestKeys.name,\n            AndroidManifestKeys.theme,\n            AndroidManifestKeys.appComponentFactory,\n        ).forEach(::applicationAttribute)\n        return globalAttributes\n    }\n\n    private fun readComponents(application: Element?) =\n        listOf(\n            AndroidManifestKeys.activity to ::parseActivity,\n            AndroidManifestKeys.service to ::parseService,\n            AndroidManifestKeys.receiver to ::parseReceiver,\n            AndroidManifestKeys.provider to ::parseProvider,\n        ).map { (componentKey, parseMethod) ->\n            val componentArray = parseComponents(application, componentKey, parseMethod)\n            componentKey to componentArray\n        }\n\n\n    private fun parseComponents(\n        application: Element?,\n        componentKey: String,\n        parseFunction: (Element) -> ComponentMap\n    ): Array<ComponentMap> {\n        if (application == null) {\n            return emptyArray()\n        }\n        val nodeList = application.getElementsByTagName(componentKey)\n        val length = nodeList.length\n        val collectionList = mutableListOf<ComponentMap>()\n        for (i in 0 until length) {\n            val node = nodeList.item(i)\n            assert(node.nodeType == Node.ELEMENT_NODE)\n            val map = parseFunction(node as Element)\n            collectionList.add(map)\n        }\n        return collectionList.toTypedArray()\n    }\n\n    private fun parseActivity(element: Element): ComponentMap {\n        val activityMap = parseComponent(element).toMutableMap()\n\n        listOf(\n            AndroidManifestKeys.theme,\n            AndroidManifestKeys.configChanges,\n            AndroidManifestKeys.windowSoftInputMode,\n            AndroidManifestKeys.screenOrientation,\n        ).forEach { attributeKey ->\n            activityMap.putAttributeIfNotNull(element, attributeKey)\n        }\n        return activityMap\n    }\n\n    private fun parseService(element: Element): ComponentMap {\n        return parseComponent(element)\n    }\n\n    private fun parseReceiver(element: Element): ComponentMap {\n        val receiverMap = parseComponent(element).toMutableMap()\n\n        val receiverActions = parseReceiverActions(element)\n        if (receiverActions.isNotEmpty()) {\n            receiverMap[AndroidManifestKeys.action] = receiverActions\n        }\n\n        return receiverMap\n    }\n\n    private fun parseReceiverActions(receiverElement: Element): List<String> {\n        val intentFilters =\n            receiverElement.getElementsByTagName(AndroidManifestKeys.`intent-filter`)\n        val collectionList = mutableListOf<String>()\n        for (i in 0 until intentFilters.length) {\n            val intentFilter = intentFilters.item(i)\n            assert(intentFilter.nodeType == Node.ELEMENT_NODE)\n            val actions = (intentFilter as Element).getElementsByTagName(AndroidManifestKeys.action)\n            for (j in 0 until actions.length) {\n                val action = actions.item(j)\n                assert(action.nodeType == Node.ELEMENT_NODE)\n                val actionName = (action as Element).getAttribute(AndroidManifestKeys.name)\n                collectionList.add(actionName)\n            }\n        }\n        return collectionList\n    }\n\n    private fun parseProvider(element: Element): ComponentMap {\n        val providerMap = parseComponent(element).toMutableMap()\n\n        providerMap.putAttributeIfNotNull(element, AndroidManifestKeys.authorities)\n        providerMap.putAttributeIfNotNull(element, AndroidManifestKeys.grantUriPermissions)\n\n        return providerMap\n    }\n\n    private fun parseComponent(element: Element): ComponentMap {\n        val componentName = element.getAttribute(AndroidManifestKeys.name)\n        return mapOf(\n            AndroidManifestKeys.name to componentName\n        )\n    }\n\n    private fun MutableComponentMap.putAttributeIfNotNull(\n        componentElement: Element,\n        attributeKey: String\n    ) {\n        if (componentElement.hasAttribute(attributeKey)) {\n            this[attributeKey] = componentElement.getAttribute(attributeKey)\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/ManifestParser.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nimport java.io.File\nimport java.util.Collections\n\n/**\n * manifest-parser的入口方法\n *\n * @param xmlFile       com.android.build.gradle.tasks.ManifestProcessorTask任务的输出文件，\n *                      一般位于apk工程的build/intermediates/merged_manifest目录中。\n * @param outputDir     生成文件的输出目录\n * @param packageName   生成类的包名\n * @param manifestValueParser 资源解析器\n */\nfun generatePluginManifest(\n    xmlFile: File,\n    outputDir: File,\n    packageName: String,\n    manifestValueParser: ManifestValueParser? = null\n) {\n    val androidManifest = AndroidManifestReader().read(xmlFile)\n    val generator = PluginManifestGenerator()\n    generator.generate(androidManifest, outputDir, packageName, manifestValueParser)\n}\n\n/**\n * 创建资源解析器。\n *\n * @param rTxt R.txt文件\n * @return 资源解析器\n */\nfun createManifestValueParser(rTxt: File): ManifestValueParser {\n    val rTxtMap = parseRTxt(rTxt)\n\n    return { resName ->\n        if (resName.startsWith(\"@android:\")) {\n            // @android:style/Theme.NoTitleBar -> android.R.style.Theme_NoTitleBar\n            val parts = resName.substringAfter(\"@android:\").split(\"/\")\n            val type = parts[0]\n            val name = parts[1].replace(\".\", \"_\")\n            \"android.R.$type.$name\"\n        } else {\n            // @[package:]type/name -> id 值\n            var raw = resName.substringAfter(\"@\")\n            if (raw.contains(\":\")) {\n                raw = raw.substringAfter(\":\")\n            }\n            val parts = raw.split(\"/\")\n            val type = parts[0]\n            val name = parts[1].replace('.', '_')\n            val key = \"@$type/$name\"\n            rTxtMap[key]\n                ?: throw IllegalArgumentException(\"Resource not found in R.txt: $resName (normalized: $key)\")\n        }\n    }\n}\n\n/**\n * 解析 R.txt 文件并生成资源 ID 映射表。 R.txt 包含项目引用的所有资源 ID。\n *\n * @param rTxtFile R.txt 文件对象\n * @return 资源全称（如 @string/app_name）到 ID 的映射\n */\nfun parseRTxt(rTxtFile: File): Map<String, String> {\n    if (!rTxtFile.exists()) return Collections.emptyMap()\n\n    val map = mutableMapOf<String, String>()\n    rTxtFile.useLines {\n        it.forEach { line ->\n            if (!(line.startsWith(\"int \"))) {\n                return@forEach\n            }\n            val parts = line.split(Regex(\"\\\\s+\")).filter { it.isNotBlank() }\n            if (parts.size == 4 && parts[0] == \"int\") {\n                val type = parts[1]\n                val name = parts[2]\n                val idStr = parts[3]\n                map[\"@$type/$name\"] = idStr\n            }\n        }\n    }\n    return map\n}\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/main/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGenerator.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nimport com.squareup.javapoet.*\nimport com.tencent.shadow.core.runtime.PluginManifest\nimport java.io.File\nimport javax.lang.model.element.Modifier\n\n/**\n * PluginManifest.java生成器\n *\n * 将Loader所需的插件Manifest信息生成为Java文件，\n * 添加runtime中PluginManifest接口的实现方法\n */\nclass PluginManifestGenerator {\n    /**\n     * 生成器入口方法\n     *\n     * 根据AndroidManifestReader输出的Map生成PluginManifest.java到outputDir目录中。\n     *\n     * @param manifestMap   AndroidManifestReader#read的输出Map\n     * @param outputDir     生成文件的输出目录\n     * @param packageName   生成类的包名\n     * @param manifestValueParser 资源值解析器。用于将资源名称解析为资源 ID 值\n     */\n    fun generate(\n        manifestMap: ManifestMap,\n        outputDir: File,\n        packageName: String,\n        manifestValueParser: ManifestValueParser? = null\n    ) {\n        val pluginManifestBuilder = PluginManifestBuilder(manifestMap, manifestValueParser)\n        val pluginManifest = pluginManifestBuilder.build()\n        JavaFile.builder(packageName, pluginManifest)\n            .build()\n            .writeTo(outputDir)\n    }\n}\n\nprivate class PluginManifestBuilder(\n    val manifestMap: ManifestMap,\n    val manifestValueParser: ManifestValueParser? = null\n) {\n    val classBuilder: TypeSpec.Builder =\n        TypeSpec.classBuilder(\"PluginManifest\")\n            .addSuperinterface(ClassName.get(PluginManifest::class.java))\n            .addModifiers(Modifier.PUBLIC)!!\n\n    fun build(): TypeSpec {\n        listOf(\n            *buildApplicationFields(),\n            buildActivityInfoArrayField(),\n            buildServiceInfoArrayField(),\n            buildReceiverInfoArrayField(),\n            buildProviderInfoArrayField(),\n        ).forEach { fieldSpec ->\n            val getterMethod = buildGetterMethod(fieldSpec)\n            classBuilder.addField(fieldSpec)\n            classBuilder.addMethod(getterMethod)\n        }\n        return classBuilder.build()\n    }\n\n    private fun buildApplicationFields(): Array<FieldSpec> {\n        val stringFields = mapOf(\n            \"applicationPackageName\" to AndroidManifestKeys.`package`,\n            \"applicationClassName\" to AndroidManifestKeys.name,\n            \"appComponentFactory\" to AndroidManifestKeys.appComponentFactory,\n        ).map { (fieldName, key) ->\n            buildStringField(fieldName, key)\n        }\n\n        val resIdFields = mapOf(\n            \"applicationTheme\" to AndroidManifestKeys.theme,\n        ).map { (fieldName, key) ->\n            buildResIdField(fieldName, key)\n        }\n\n        return (stringFields + resIdFields).toTypedArray()\n    }\n\n    private fun buildActivityInfoArrayField() = buildComponentArrayField(\n        AndroidManifestKeys.activity,\n        \"ActivityInfo\",\n        \"activities\",\n        ::toNewActivityInfo,\n    )\n\n    private fun buildServiceInfoArrayField() = buildComponentArrayField(\n        AndroidManifestKeys.service,\n        \"ServiceInfo\",\n        \"services\",\n        ::toNewServiceInfo,\n    )\n\n    private fun buildReceiverInfoArrayField() = buildComponentArrayField(\n        AndroidManifestKeys.receiver,\n        \"ReceiverInfo\",\n        \"receivers\",\n        ::toNewReceiverInfo,\n    )\n\n    private fun buildProviderInfoArrayField() = buildComponentArrayField(\n        AndroidManifestKeys.provider,\n        \"ProviderInfo\",\n        \"providers\",\n        ::toNewProviderInfo,\n    )\n\n    private fun buildComponentArrayField(\n        key: String,\n        subClassName: String,\n        fieldName: String,\n        transform: (ComponentMap) -> String\n    ): FieldSpec {\n        @Suppress(\"UNCHECKED_CAST\")\n        val componentMapArray = manifestMap[key] as Array<ComponentMap>\n        val literal = componentMapArray.joinToString(\n            separator = \",\\n\",\n            prefix = \"{\\n\",\n            postfix = \"\\n}\",\n            transform = transform\n        )\n\n        val componentInfoArrayTypeName = ArrayTypeName.of(\n            ClassName.get(\n                \"com.tencent.shadow.core.runtime\",\n                \"PluginManifest\",\n                subClassName\n            )\n        )\n\n        val codeBlock = if (componentMapArray.isNotEmpty()) {\n            CodeBlock.of(\"new \\$1T \\$2L\", componentInfoArrayTypeName, literal)\n        } else {\n            nullCodeBlock()\n        }\n        return privateStaticFinalFieldBuilder(\n            componentInfoArrayTypeName,\n            fieldName,\n        ).initializer(\n            codeBlock\n        ).build()\n    }\n\n    private fun buildStringField(fieldName: String, key: String): FieldSpec {\n        val value = manifestMap[key]\n        val codeBlock = if (value != null) {\n            CodeBlock.of(\"\\\"$1L\\\"\", value)\n        } else {\n            nullCodeBlock()\n        }\n        return privateStaticFinalStringFieldBuilder(fieldName)\n            .initializer(codeBlock).build()\n    }\n\n    private fun buildGetterMethod(fieldSpec: FieldSpec): MethodSpec =\n        MethodSpec.methodBuilder(\"get${fieldSpec.name.capitalize()}\")\n            .addModifiers(\n                Modifier.PUBLIC,\n                Modifier.FINAL,\n            )\n            .returns(fieldSpec.type)\n            .addStatement(CodeBlock.of(\"return ${fieldSpec.name}\"))\n            .build()\n\n    private fun buildResIdField(fieldName: String, key: String): FieldSpec {\n        val manifestValue = manifestMap[key]\n        return if (manifestValue != null) {\n            buildResIdFieldWithValue(fieldName, manifestValue)\n        } else {\n            privateStaticFinalIntFieldBuilder(fieldName)\n                .initializer(\n                    CodeBlock.of(\"$1L\", \"0\")\n                ).build()\n        }\n    }\n\n    private fun buildResIdFieldWithValue(\n        fieldName: String,\n        manifestValue: Any,\n    ): FieldSpec {\n\n        val resIdLiteral = themeStringToResId(manifestValue, manifestValueParser)\n        return privateStaticFinalIntFieldBuilder(fieldName)\n            .initializer(\n                CodeBlock.of(\"$1L\", resIdLiteral)\n            ).build()\n    }\n\n\n    private fun toNewActivityInfo(componentMap: ComponentMap): String {\n        fun makeResIdLiteral(\n            key: String,\n            defaultValue: String = \"0\",\n            valueToResId: (value: String) -> String\n        ): String {\n            val value = componentMap[key] as String?\n            val literal = if (value != null) {\n                valueToResId(value)\n            } else {\n                defaultValue\n            }\n            return literal\n        }\n\n        val themeLiteral = makeResIdLiteral(AndroidManifestKeys.theme) {\n            themeStringToResId(it, manifestValueParser)\n        }\n        val configChangesLiteral = makeResIdLiteral(AndroidManifestKeys.configChanges) {\n            parseConfigChanges(it)\n        }\n        val softInputModeLiteral = makeResIdLiteral(AndroidManifestKeys.windowSoftInputMode) {\n            parseSoftInputMode(it)\n        }\n\n        val screenOrientation = makeResIdLiteral(AndroidManifestKeys.screenOrientation, \"-1\") {\n            parseScreenOrientation(it)\n        }\n\n        return \"new com.tencent.shadow.core.runtime.PluginManifest\" +\n                \".ActivityInfo(\" +\n                \"\\\"${componentMap[AndroidManifestKeys.name]}\\\", \" +\n                \"$themeLiteral ,\" +\n                \"$configChangesLiteral ,\" +\n                \"$softInputModeLiteral ,\" +\n                screenOrientation +\n                \")\"\n    }\n\n    private fun toNewServiceInfo(componentMap: ComponentMap): String {\n        return \"new com.tencent.shadow.core.runtime.PluginManifest\" +\n                \".ServiceInfo(\\\"${componentMap[AndroidManifestKeys.name]}\\\")\"\n    }\n\n    private fun toNewReceiverInfo(componentMap: ComponentMap): String {\n        @Suppress(\"UNCHECKED_CAST\")\n        val actions = componentMap[AndroidManifestKeys.action] as List<String>?\n        val actionsLiteral =\n            actions?.joinToString(\n                prefix = \"new String[]{\\\"\",\n                separator = \"\\\", \\\"\",\n                postfix = \"\\\"}\"\n            ) ?: \"null\"\n\n        return \"new com.tencent.shadow.core.runtime.PluginManifest\" +\n                \".ReceiverInfo(\\\"${componentMap[AndroidManifestKeys.name]}\\\", \" +\n                actionsLiteral +\n                \")\"\n    }\n\n    private fun toNewProviderInfo(componentMap: ComponentMap): String {\n        val authoritiesValue = componentMap[AndroidManifestKeys.authorities]\n        //如果未传值使用android.content.pm.ProviderInfo.grantUriPermissions的默认值false\n        val grantUriPermissions = componentMap[AndroidManifestKeys.grantUriPermissions] ?: false\n\n        val authoritiesLiteral =\n            if (authoritiesValue != null) {\n                \"\\\"${authoritiesValue}\\\"\"\n            } else {\n                \"null\"\n            }\n\n        return \"new com.tencent.shadow.core.runtime.PluginManifest\" +\n                \".ProviderInfo(\\\"${componentMap[AndroidManifestKeys.name]}\\\", $authoritiesLiteral,$grantUriPermissions)\"\n    }\n\n    companion object {\n        fun privateStaticFinalFieldBuilder(type: TypeName, fieldName: String) = FieldSpec.builder(\n            type,\n            fieldName,\n            Modifier.PRIVATE,\n            Modifier.STATIC,\n            Modifier.FINAL,\n        )!!\n\n        fun privateStaticFinalStringFieldBuilder(fieldName: String) =\n            privateStaticFinalFieldBuilder(\n                ClassName.get(String::class.java),\n                fieldName,\n            )\n\n        fun privateStaticFinalIntFieldBuilder(fieldName: String) =\n            privateStaticFinalFieldBuilder(\n                TypeName.INT,\n                fieldName,\n            )\n\n        fun nullCodeBlock() = CodeBlock.of(\"null\")!!\n\n        fun themeStringToResId(\n            manifestValue: Any,\n            manifestValueParser: ManifestValueParser? = null\n        ): String {\n            val formatValue = manifestValue as String // for example: @ref/0x7e0b009e\n            if (formatValue.startsWith(\"@ref/\")) {\n                return formatValue.removePrefix(\"@ref/\")\n            } else if (formatValue.startsWith(\"@\")) {\n                // 对于使用 merged manifest 的场景，manifestValueParser 不会为 null 。\n                // 对于使用 ap_ 文件中的 AndroidManifest.xml 的场景，不会出现以 @ 打头却不是 @ref 的情况。\n                if (manifestValueParser != null) {\n                    // @style/Theme.AppCompat --> id 值\n                    return manifestValueParser.invoke(formatValue)\n                }\n            }\n            // 其余格式：https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:apkparser/analyzer/src/main/java/com/android/tools/apk/analyzer/BinaryXmlParser.java;l=193\n            throw TODO(\"不支持其他格式: $formatValue\")\n        }\n\n        fun parseConfigChanges(value: String): String {\n            if (value.startsWith(\"0x\") || value.toIntOrNull() != null) return value\n            return value.split(\"|\").joinToString(\"|\") {\n                val constant = when (it) {\n                    \"mcc\" -> \"CONFIG_MCC\"\n                    \"mnc\" -> \"CONFIG_MNC\"\n                    \"locale\" -> \"CONFIG_LOCALE\"\n                    \"touchscreen\" -> \"CONFIG_TOUCHSCREEN\"\n                    \"keyboard\" -> \"CONFIG_KEYBOARD\"\n                    \"keyboardHidden\" -> \"CONFIG_KEYBOARD_HIDDEN\"\n                    \"navigation\" -> \"CONFIG_NAVIGATION\"\n                    \"orientation\" -> \"CONFIG_ORIENTATION\"\n                    \"screenLayout\" -> \"CONFIG_SCREEN_LAYOUT\"\n                    \"uiMode\" -> \"CONFIG_UI_MODE\"\n                    \"screenSize\" -> \"CONFIG_SCREEN_SIZE\"\n                    \"smallestScreenSize\" -> \"CONFIG_SMALLEST_SCREEN_SIZE\"\n                    \"density\" -> \"CONFIG_DENSITY\"\n                    \"layoutDirection\" -> \"CONFIG_LAYOUT_DIRECTION\"\n                    \"fontScale\" -> \"CONFIG_FONT_SCALE\"\n                    \"colorMode\" -> \"CONFIG_COLOR_MODE\" // Added in API 26\n                    else -> throw IllegalArgumentException(\"Unknown configChanges: $it\")\n                }\n                \"android.content.pm.ActivityInfo.$constant\"\n            }\n        }\n\n        fun parseSoftInputMode(value: String): String {\n            if (value.startsWith(\"0x\") || value.toIntOrNull() != null) return value\n            return value.split(\"|\").joinToString(\"|\") {\n                val constant = when (it) {\n                    \"stateUnspecified\" -> \"SOFT_INPUT_STATE_UNSPECIFIED\"\n                    \"stateUnchanged\" -> \"SOFT_INPUT_STATE_UNCHANGED\"\n                    \"stateHidden\" -> \"SOFT_INPUT_STATE_HIDDEN\"\n                    \"stateAlwaysHidden\" -> \"SOFT_INPUT_STATE_ALWAYS_HIDDEN\"\n                    \"stateVisible\" -> \"SOFT_INPUT_STATE_VISIBLE\"\n                    \"stateAlwaysVisible\" -> \"SOFT_INPUT_STATE_ALWAYS_VISIBLE\"\n                    \"adjustUnspecified\" -> \"SOFT_INPUT_ADJUST_UNSPECIFIED\"\n                    \"adjustResize\" -> \"SOFT_INPUT_ADJUST_RESIZE\"\n                    \"adjustPan\" -> \"SOFT_INPUT_ADJUST_PAN\"\n                    \"adjustNothing\" -> \"SOFT_INPUT_ADJUST_NOTHING\"\n                    \"isForwardNavigation\" -> \"SOFT_INPUT_IS_FORWARD_NAVIGATION\"\n                    else -> throw IllegalArgumentException(\"Unknown windowSoftInputMode: $it\")\n                }\n                \"android.view.WindowManager.LayoutParams.$constant\"\n            }\n        }\n\n        fun parseScreenOrientation(value: String): String {\n            if (value.startsWith(\"0x\") || value.toIntOrNull() != null) return value\n            val constant = when (value) {\n                \"unspecified\" -> \"SCREEN_ORIENTATION_UNSPECIFIED\"\n                \"landscape\" -> \"SCREEN_ORIENTATION_LANDSCAPE\"\n                \"portrait\" -> \"SCREEN_ORIENTATION_PORTRAIT\"\n                \"user\" -> \"SCREEN_ORIENTATION_USER\"\n                \"behind\" -> \"SCREEN_ORIENTATION_BEHIND\"\n                \"sensor\" -> \"SCREEN_ORIENTATION_SENSOR\"\n                \"nosensor\" -> \"SCREEN_ORIENTATION_NOSENSOR\"\n                \"sensorLandscape\" -> \"SCREEN_ORIENTATION_SENSOR_LANDSCAPE\"\n                \"sensorPortrait\" -> \"SCREEN_ORIENTATION_SENSOR_PORTRAIT\"\n                \"reverseLandscape\" -> \"SCREEN_ORIENTATION_REVERSE_LANDSCAPE\"\n                \"reversePortrait\" -> \"SCREEN_ORIENTATION_REVERSE_PORTRAIT\"\n                \"fullSensor\" -> \"SCREEN_ORIENTATION_FULL_SENSOR\"\n                \"userLandscape\" -> \"SCREEN_ORIENTATION_USER_LANDSCAPE\"\n                \"userPortrait\" -> \"SCREEN_ORIENTATION_USER_PORTRAIT\"\n                \"fullUser\" -> \"SCREEN_ORIENTATION_FULL_USER\"\n                \"locked\" -> \"SCREEN_ORIENTATION_LOCKED\"\n                else -> throw IllegalArgumentException(\"Unknown screenOrientation: $value\")\n            }\n            return \"android.content.pm.ActivityInfo.$constant\"\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/AndroidManifestReaderTest.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nimport org.junit.Assert\nimport org.junit.Test\nimport java.io.File\n\nclass AndroidManifestReaderTest {\n    @Test\n    fun testReadXml() {\n        val testFile = File(javaClass.classLoader.getResource(\"sample-app.xml\")!!.toURI())\n        val androidManifest = AndroidManifestReader().read(testFile)\n        Assert.assertEquals(\n            \"com.tencent.shadow.sample.host\",\n            androidManifest[AndroidManifestKeys.`package`]\n        )\n        Assert.assertEquals(\n            \"com.tencent.shadow.sample.plugin.app.lib.UseCaseApplication\",\n            androidManifest[AndroidManifestKeys.name]\n        )\n        Assert.assertEquals(\n            \"com.tencent.shadow.test.plugin.androidx_cases.lib.TestComponentFactory\",\n            androidManifest[AndroidManifestKeys.appComponentFactory]\n        )\n        Assert.assertEquals(\n            \"@ref/0x01030006\",\n            androidManifest[AndroidManifestKeys.theme]\n        )\n    }\n}"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/test/kotlin/com/tencent/shadow/core/manifest_parser/PluginManifestGeneratorTest.kt",
    "content": "package com.tencent.shadow.core.manifest_parser\n\nimport org.junit.Assert\nimport org.junit.Test\nimport java.io.File\n\nclass PluginManifestGeneratorTest {\n\n    @Test\n    fun testCompileCaseAsLittleAsPossible() {\n        testCompile(\"case_as_little_as_possible.xml\")\n    }\n\n    @Test\n    fun testNoAppComponentFactory() {\n        testCompile(\"noAppComponentFactory.xml\")\n    }\n\n    @Test\n    fun testCompileSampleApp() {\n        testCompile(\"sample-app.xml\")\n    }\n\n    private fun testCompile(case: String) {\n        val testFile = File(javaClass.classLoader.getResource(case)!!.toURI())\n        val androidManifest = AndroidManifestReader().read(testFile)\n        val generator = PluginManifestGenerator()\n\n        val tempBuildDir = File(\"build\", \"PluginManifestGeneratorTest\")\n        val outputDir = File(tempBuildDir, case)\n        println(\"outputDir==$outputDir\")\n        generator.generate(androidManifest, outputDir, \"test\")\n\n        val cmd = \"javac -cp ../runtime/build/classes/java/main:build/classes/java/test\" +\n                \" ${outputDir.absolutePath}/test/PluginManifest.java\"\n        val process = Runtime.getRuntime().exec(cmd)\n        val ret = process.waitFor()\n        Assert.assertEquals(cmd, 0, ret)\n    }\n}"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/test/resources/case_as_little_as_possible.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.test.hostapp\" android:versionCode=\"1\" android:versionName=\"local\">\n\n    <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"28\" />\n\n</manifest>"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/test/resources/noAppComponentFactory.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.test.hostapp\" android:versionCode=\"1\" android:versionName=\"local\">\n\n    <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"28\" />\n    <application android:name=\"com.tencent.shadow.sample.plugin.app.lib.UseCaseApplication\" />\n</manifest>"
  },
  {
    "path": "projects/sdk/core/manifest-parser/src/test/resources/sample-app.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"1\"\n    android:versionName=\"local\" android:compileSdkVersion=\"31\"\n    android:compileSdkVersionCodename=\"12\" package=\"com.tencent.shadow.sample.host\"\n    platformBuildVersionCode=\"31\" platformBuildVersionName=\"12\">\n\n    <uses-sdk android:minSdkVersion=\"14\" android:targetSdkVersion=\"28\" />\n\n    <uses-feature android:glEsVersion=\"0x20000\" />\n\n    <application android:theme=\"@ref/0x01030006\" android:label=\"@ref/0x7e070000\"\n        android:icon=\"@ref/0x01080093\"\n        android:appComponentFactory=\"com.tencent.shadow.test.plugin.androidx_cases.lib.TestComponentFactory\"\n        android:name=\"com.tencent.shadow.sample.plugin.app.lib.UseCaseApplication\"\n        android:debuggable=\"true\">\n\n        <meta-data android:name=\"test_meta\" android:value=\"test_value\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreate\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivitySetTheme\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOptionMenu\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOnCreate\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityOrientation\"\n            android:configChanges=\"0x40000480\" android:windowSoftInputMode=\"0x10\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityWindowSoftMode\"\n            android:windowSoftInputMode=\"0x4\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestDBContentProviderActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.activity.TestActivityReCreateBySystem\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestReceiverActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.TestDynamicReceiverActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestDynamicFragmentActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestXmlFragmentActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.dialog.TestDialogActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.packagemanager.TestPackageManagerActivity\" />\n\n        <receiver\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver\">\n\n            <intent-filter>\n\n                <action android:name=\"com.tencent.test.action\" />\n            </intent-filter>\n        </receiver>\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestFileProviderActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.application.TestApplicationActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.context.ActivityContextSubDirTestActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.context.ApplicationContextSubDirTestActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.host_communication.PluginUseHostClassActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.webview.WebViewActivity\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.fragment.TestDialogFragmentActivity\" />\n\n        <provider\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.provider.TestProvider\"\n            android:authorities=\"com.tencent.shadow.sample.host.provider.test\" />\n\n        <service\n            android:name=\"com.tencent.shadow.sample.plugin.app.lib.usecases.service.HostAddPluginViewService\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "projects/sdk/core/runtime/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/runtime/build.gradle",
    "content": "/**\n * shadow-runtime是运行时Shadow App所依赖的类。\n *\n * 这里面的类混淆意义不大，对体积缩减需求也不强。\n * shadow-loader的Debug版依赖这个模块的Debug版\n * shadow-loader的Release版依赖这个模块的Release版\n * shadow-transform依赖这个模块的Release版\n * 因此暂定这个模块不混淆，保持Debug版和Release版无差别。\n */\n\nimport com.tencent.shadow.coding.code_generator.ActivityCodeGenerator\n\nbuildscript {\n    dependencies {\n        classpath 'com.tencent.shadow.coding:android-jar'\n        classpath 'com.tencent.shadow.coding:code-generator'\n    }\n}\n\napply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\njava {\n    sourceSets {\n        main.java.srcDirs += 'build/generated/sources/code-generator'\n    }\n}\n\ndependencies{\n    compileOnly project(':activity-container')\n}\n\ndef generateCode = tasks.register('generateCode') {\n    def outputDir = layout.buildDirectory.dir('generated/sources/code-generator')\n    outputs.dir(outputDir)\n            .withPropertyName('outputDir')\n    doLast {\n        ActivityCodeGenerator codeGenerator = new ActivityCodeGenerator()\n        codeGenerator.generate(outputDir.get().getAsFile(), \"runtime\")\n    }\n}\n\ncompileJava.dependsOn(generateCode)\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ActivityOptionsSupport.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.util.Pair;\nimport android.view.View;\n\n/**\n * @see com.tencent.shadow.core.transform.specific.ActivityOptionsSupportTransform\n */\n@SuppressLint(\"NewApi\")\npublic class ActivityOptionsSupport {\n\n    public static ActivityOptions makeSceneTransitionAnimation(\n            ShadowActivity shadowActivity,\n            View sharedElement,\n            String sharedElementName) {\n        Activity activity = shadowActivity.hostActivityDelegator\n                .getHostActivity().getImplementActivity();\n        return ActivityOptions.makeSceneTransitionAnimation(\n                activity,\n                sharedElement,\n                sharedElementName\n        );\n    }\n\n    @SafeVarargs\n    public static ActivityOptions makeSceneTransitionAnimation(\n            ShadowActivity shadowActivity,\n            Pair<View, String>... sharedElements) {\n        Activity activity = shadowActivity.hostActivityDelegator\n                .getHostActivity().getImplementActivity();\n        return ActivityOptions.makeSceneTransitionAnimation(\n                activity,\n                sharedElements\n        );\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/FixedContextLayoutInflater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\n/**\n * 在HostActivityDelegate.getLayoutInflater返回的LayoutInflater虽然已经被替换为ShadowActivity作为Context了.\n * 但是Fragment在创建时还是会通过这个LayoutInflater的cloneInContext方法,传入宿主Activity作为新的Context.\n * 这里通过覆盖cloneInContext方法,避免Context被替换.\n * 见onGetLayoutInflater() of Activity$HostCallbacks in Activity.java\n *\n * @author cubershi\n */\npublic abstract class FixedContextLayoutInflater extends LayoutInflater {\n    /**\n     * 复制自\n     * com.android.internal.policy.PhoneLayoutInflater#sClassPrefixList\n     */\n    private static final String[] sClassPrefixList = {\n            \"android.widget.\",\n            \"android.webkit.\",\n            \"android.app.\"\n    };\n\n    public FixedContextLayoutInflater(Context context) {\n        super(context);\n    }\n\n    public FixedContextLayoutInflater(LayoutInflater original, Context newContext) {\n        super(original, newContext);\n    }\n\n    @Override\n    protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {\n        //模仿com.android.internal.policy.PhoneLayoutInflater#onCreateView实现\n        //xml中一些系统view省略了包名，这里在尝试拼上包名\n        for (String prefix : sClassPrefixList) {\n            try {\n                Pair<String, String> afterChange = changeViewNameAndPrefix(name, prefix);\n                name = afterChange.first;\n                prefix = afterChange.second;\n                View view = createView(name, prefix, attrs);\n                if (view != null) {\n                    return view;\n                }\n            } catch (ClassNotFoundException e) {\n                // In this case we want to let the base class take a crack\n                // at it.\n            }\n        }\n\n        return super.onCreateView(name, attrs);\n    }\n\n    @Override\n    public LayoutInflater cloneInContext(Context newContext) {\n        return createNewContextLayoutInflater(newContext);\n    }\n\n    abstract LayoutInflater createNewContextLayoutInflater(Context context);\n\n    abstract Pair<String, String> changeViewNameAndPrefix(String name, String prefix);\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.pm.VersionedPackage;\nimport android.os.Build;\n\nimport java.util.List;\n\n/**\n * PackageManagerTransform必须把对PackageManager的调用转到到这个处于Runtime层不被Transform作用的类上来，\n * 而不能把这个类实现的方法直接写在Transform生成的方法体中，是为了避免这个类实现的代码再次被Transform，\n * 形成循环调用。\n */\npublic class PackageManagerInvokeRedirect {\n\n    public static PluginPackageManager getPluginPackageManager(ClassLoader classLoaderOfInvokeCode) {\n        return PluginPartInfoManager.getPluginInfo(classLoaderOfInvokeCode).packageManager;\n    }\n\n    public static ApplicationInfo getApplicationInfo(ClassLoader classLoaderOfInvokeCode, String packageName, int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getApplicationInfo(packageName, flags);\n    }\n\n    public static ActivityInfo getActivityInfo(ClassLoader classLoaderOfInvokeCode, ComponentName component, int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getActivityInfo(component, flags);\n    }\n\n    public static ServiceInfo getServiceInfo(ClassLoader classLoaderOfInvokeCode, ComponentName component, int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getServiceInfo(component, flags);\n    }\n\n    public static ProviderInfo getProviderInfo(ClassLoader classLoaderOfInvokeCode, ComponentName component, int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getProviderInfo(component, flags);\n    }\n\n    public static PackageInfo getPackageInfo(ClassLoader classLoaderOfInvokeCode, String packageName, int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getPackageInfo(packageName, flags);\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    public static PackageInfo getPackageInfo(ClassLoader classLoaderOfInvokeCode, VersionedPackage versionedPackage,\n                                             int flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getPackageInfo(versionedPackage, flags);\n    }\n\n    @TargetApi(Build.VERSION_CODES.TIRAMISU)\n    public static PackageInfo getPackageInfo(ClassLoader classLoaderOfInvokeCode, String packageName,\n                                             PackageManager.PackageInfoFlags flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getPackageInfo(packageName, flags);\n    }\n\n    @TargetApi(Build.VERSION_CODES.TIRAMISU)\n    public static PackageInfo getPackageInfo(ClassLoader classLoaderOfInvokeCode, VersionedPackage versionedPackage,\n                                             PackageManager.PackageInfoFlags flags) throws PackageManager.NameNotFoundException {\n        return getPluginPackageManager(classLoaderOfInvokeCode).getPackageInfo(versionedPackage, flags);\n    }\n\n    public static ProviderInfo resolveContentProvider(ClassLoader classLoaderOfInvokeCode, String name, int flags) {\n        return getPluginPackageManager(classLoaderOfInvokeCode).resolveContentProvider(name, flags);\n    }\n\n    public static List<ProviderInfo> queryContentProviders(ClassLoader classLoaderOfInvokeCode, String processName, int uid, int flags) {\n        return getPluginPackageManager(classLoaderOfInvokeCode).queryContentProviders(processName, uid, flags);\n    }\n\n    public static ResolveInfo resolveActivity(ClassLoader classLoaderOfInvokeCode, Intent intent, int flags) {\n        return getPluginPackageManager(classLoaderOfInvokeCode).resolveActivity(intent, flags);\n    }\n\n    public static ResolveInfo resolveService(ClassLoader classLoaderOfInvokeCode, Intent intent, int flags) {\n        return getPluginPackageManager(classLoaderOfInvokeCode).resolveService(intent, flags);\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.Window;\n\nimport com.tencent.shadow.core.runtime.container.HostActivityDelegator;\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic abstract class PluginActivity extends GeneratedPluginActivity {\n\n    static PluginActivity get(PluginContainerActivity pluginContainerActivity) {\n        Object o = pluginContainerActivity.getPluginActivity();\n        if (o != null) {\n            return (PluginActivity) o;\n        } else {\n            //在遇到IllegalIntent时hostActivityDelegate==null。需要返回一个空的Activity避免Crash。\n            return new ShadowActivity();\n        }\n    }\n\n    HostActivityDelegator hostActivityDelegator;\n\n    ShadowApplication mPluginApplication;\n\n    ComponentName mCallingActivity;\n\n    public void registerActivityLifecycleCallbacks(\n            ShadowActivityLifecycleCallbacks callback) {\n        mPluginApplication.mActivityLifecycleCallbacksHolder.registerActivityLifecycleCallbacks(\n                callback, this, hostActivityDelegator\n        );\n    }\n\n    public void unregisterActivityLifecycleCallbacks(\n            ShadowActivityLifecycleCallbacks callback) {\n        mPluginApplication.mActivityLifecycleCallbacksHolder.unregisterActivityLifecycleCallbacks(\n                callback, this, hostActivityDelegator\n        );\n    }\n\n    public final void setHostContextAsBase(Context context) {\n        attachBaseContext(context);\n    }\n\n    public void setHostActivityDelegator(HostActivityDelegator delegator) {\n        super.hostActivityDelegator = delegator;\n        hostActivityDelegator = delegator;\n    }\n\n    public void setPluginApplication(ShadowApplication pluginApplication) {\n        mPluginApplication = pluginApplication;\n    }\n\n    public boolean onCreatePanelMenu(int featureId, Menu menu) {\n        if (featureId == Window.FEATURE_OPTIONS_PANEL) {\n            return onCreateOptionsMenu(menu);\n        } else {\n            return hostActivityDelegator.superOnCreatePanelMenu(featureId, menu);\n        }\n    }\n\n    public LayoutInflater getLayoutInflater() {\n        return LayoutInflater.from(this);\n    }\n\n    //TODO: 对齐原手工代码，这个方法签名实际上不对，应该传入ShadowActivity\n    public void onChildTitleChanged(Activity childActivity, CharSequence title) {\n        hostActivityDelegator.superOnChildTitleChanged(childActivity, title);\n    }\n\n    @Override\n    public boolean onNavigateUpFromChild(ShadowActivity arg0) {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public void onChildTitleChanged(ShadowActivity arg0, CharSequence arg1) {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    public void setCallingActivity(ComponentName callingActivity) {\n        mCallingActivity = callingActivity;\n    }\n\n    @Override\n    public void setTheme(int resid) {\n        super.setTheme(resid);\n        hostActivityDelegator.setTheme(resid);\n    }\n\n    @Override\n    public Object getSystemService(String name) {\n        if (LAYOUT_INFLATER_SERVICE.equals(name)) {\n            return super.getSystemService(name);\n        } else {\n            return hostActivityDelegator.getHostActivity().getImplementActivity()\n                    .getSystemService(name);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginManifest.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic interface PluginManifest {\n    /**\n     * same as android.content.pm.PackageItemInfo#packageName\n     */\n    String getApplicationPackageName();\n\n    /**\n     * same as android.content.pm.ApplicationInfo#className\n     */\n    String getApplicationClassName();\n\n    /**\n     * same as android.content.pm.ApplicationInfo#appComponentFactory\n     */\n    String getAppComponentFactory();\n\n    /**\n     * same as android.content.pm.ApplicationInfo#theme\n     */\n    int getApplicationTheme();\n\n    ActivityInfo[] getActivities();\n\n    ServiceInfo[] getServices();\n\n    ReceiverInfo[] getReceivers();\n\n    ProviderInfo[] getProviders();\n\n    abstract class ComponentInfo implements Parcelable {\n        public final String className;\n\n        public ComponentInfo(String className) {\n            this.className = className;\n        }\n\n        protected ComponentInfo(Parcel in) {\n            className = in.readString();\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            dest.writeString(className);\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        public static final Creator<ComponentInfo> CREATOR = new Creator<ComponentInfo>() {\n            @Override\n            public ComponentInfo createFromParcel(Parcel in) {\n                throw new UnsupportedOperationException();\n            }\n\n            @Override\n            public ComponentInfo[] newArray(int size) {\n                return new ComponentInfo[size];\n            }\n        };\n    }\n\n    final class ActivityInfo extends ComponentInfo implements Parcelable {\n        public final int theme;\n        public final int configChanges;\n        public final int softInputMode;\n        public final int screenOrientation;\n\n        public ActivityInfo(String className,\n                            int theme,\n                            int configChanges,\n                            int softInputMode,\n                            int screenOrientation) {\n            super(className);\n            this.theme = theme;\n            this.configChanges = configChanges;\n            this.softInputMode = softInputMode;\n            this.screenOrientation = screenOrientation;\n        }\n\n        protected ActivityInfo(Parcel in) {\n            super(in);\n            theme = in.readInt();\n            configChanges = in.readInt();\n            softInputMode = in.readInt();\n            screenOrientation = in.readInt();\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(theme);\n            dest.writeInt(configChanges);\n            dest.writeInt(softInputMode);\n            dest.writeInt(screenOrientation);\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        public static final Creator<ActivityInfo> CREATOR = new Creator<ActivityInfo>() {\n            @Override\n            public ActivityInfo createFromParcel(Parcel in) {\n                return new ActivityInfo(in);\n            }\n\n            @Override\n            public ActivityInfo[] newArray(int size) {\n                return new ActivityInfo[size];\n            }\n        };\n    }\n\n    final class ServiceInfo extends ComponentInfo {\n\n        public ServiceInfo(String className) {\n            super(className);\n        }\n    }\n\n    final class ReceiverInfo extends ComponentInfo {\n        public final String[] actions;\n\n        public ReceiverInfo(String className, String[] actions) {\n            super(className);\n            this.actions = actions;\n        }\n    }\n\n    final class ProviderInfo extends ComponentInfo {\n        public final String authorities;\n        public final boolean grantUriPermissions;\n\n        public ProviderInfo(String className, String authorities, boolean grantUriPermissions) {\n            super(className);\n            this.authorities = authorities;\n            this.grantUriPermissions = grantUriPermissions;\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginPackageManager.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.pm.VersionedPackage;\nimport android.os.Build;\n\nimport java.util.List;\n\npublic interface PluginPackageManager {\n    ApplicationInfo getApplicationInfo(String packageName, int flags);\n\n    ActivityInfo getActivityInfo(ComponentName component, int flags);\n\n    ServiceInfo getServiceInfo(ComponentName component, int flags);\n\n    ProviderInfo getProviderInfo(ComponentName component, int flags);\n\n    PackageInfo getPackageInfo(String packageName, int flags);\n\n    PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags);\n\n    @TargetApi(Build.VERSION_CODES.TIRAMISU)\n    PackageInfo getPackageInfo(VersionedPackage versionedPackage, PackageManager.PackageInfoFlags flags);\n\n    @TargetApi(Build.VERSION_CODES.TIRAMISU)\n    PackageInfo getPackageInfo(String packageName, PackageManager.PackageInfoFlags flags);\n\n    ProviderInfo resolveContentProvider(String name, int flags);\n\n    List<ProviderInfo> queryContentProviders(String processName, int uid, int flags);\n\n    ResolveInfo resolveActivity(Intent intent, int flags);\n\n    ResolveInfo resolveService(Intent intent, int flags);\n\n    String getArchiveFilePath();\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginPartInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.res.Resources;\n\npublic class PluginPartInfo {\n\n    public ShadowApplication application;\n\n    public Resources resources;\n\n    public ClassLoader classLoader;\n\n    PluginPackageManager packageManager;\n\n\n    public PluginPartInfo(ShadowApplication application, Resources resources, ClassLoader classLoader, PluginPackageManager packageManager) {\n        this.application = application;\n        this.resources = resources;\n        this.classLoader = classLoader;\n        this.packageManager = packageManager;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/PluginPartInfoManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class PluginPartInfoManager {\n\n    private static Map<ClassLoader, PluginPartInfo> sPluginInfos = new HashMap<>();\n\n    public static void addPluginInfo(ClassLoader classLoader, PluginPartInfo pluginPartInfo) {\n        sPluginInfos.put(classLoader, pluginPartInfo);\n    }\n\n    public static PluginPartInfo getPluginInfo(ClassLoader classLoader) {\n        PluginPartInfo pluginPartInfo = sPluginInfos.get(classLoader);\n        if (pluginPartInfo == null) {\n            throw new RuntimeException(\"没有找到classLoader对应的pluginInfo classLoader:\" + classLoader);\n        }\n        return pluginPartInfo;\n    }\n\n\n    public static Collection<PluginPartInfo> getAllPluginInfo() {\n        return sPluginInfos.values();\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ResolverHook.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.CancellationSignal;\n\npublic class ResolverHook {\n\n    public static Uri insert(ContentResolver resolver, Uri url, ContentValues values) {\n        Uri containerUri = UriConverter.parse(url.toString());\n        return resolver.insert(containerUri, values);\n    }\n\n    public static int delete(ContentResolver resolver, Uri url, String where, String[] selectionArgs) {\n        Uri containerUri = UriConverter.parse(url.toString());\n        return resolver.delete(containerUri, where, selectionArgs);\n    }\n\n    public static int update(ContentResolver resolver, Uri uri, ContentValues values, String where, String[] selectionArgs) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        return resolver.update(containerUri, values, where, selectionArgs);\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    public static Cursor query(ContentResolver resolver, Uri uri, String[] projection, Bundle queryArgs,\n                               CancellationSignal cancellationSignal) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        return resolver.query(containerUri, projection, queryArgs, cancellationSignal);\n    }\n\n    public static Cursor query(ContentResolver resolver, Uri uri, String[] projection, String selection,\n                               String[] selectionArgs, String sortOrder) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        return resolver.query(containerUri, projection, selection, selectionArgs, sortOrder);\n    }\n\n    public static Cursor query(ContentResolver resolver, Uri uri, String[] projection, String selection,\n                               String[] selectionArgs, String sortOrder,\n                               CancellationSignal cancellationSignal) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            return resolver.query(containerUri, projection, selection, selectionArgs, sortOrder, cancellationSignal);\n        } else {\n            return null;\n        }\n    }\n\n    public static Bundle call(ContentResolver resolver, Uri uri, String method, String arg, Bundle extras) {\n        if (extras == null) {\n            extras = new Bundle();\n        }\n        Uri containerUri = UriConverter.parseCall(uri.toString(), extras);\n        return resolver.call(containerUri, method, arg, extras);\n    }\n\n    public static int bulkInsert(ContentResolver resolver, Uri url, ContentValues[] values) {\n        Uri containerUri = UriConverter.parse(url.toString());\n        return resolver.bulkInsert(containerUri, values);\n    }\n\n\n}"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.IntentSender;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\npublic class ShadowActivity extends PluginActivity {\n\n    @Override\n    public void setContentView(int layoutResID) {\n        if (\"merge\".equals(XmlPullParserUtil.getLayoutStartTagName(getResources(), layoutResID))) {\n            //如果传进来的xml文件的根tag是merge时，需要特殊处理\n            View decorView = hostActivityDelegator.getWindow().getDecorView();\n            ViewGroup viewGroup = decorView.findViewById(android.R.id.content);\n            LayoutInflater.from(this).inflate(layoutResID, viewGroup);\n        } else {\n            View inflate = LayoutInflater.from(this).inflate(layoutResID, null);\n            hostActivityDelegator.setContentView(inflate);\n        }\n    }\n\n    @Override\n    public final ShadowApplication getApplication() {\n        return mPluginApplication;\n    }\n\n    @Override\n    public final ShadowActivity getParent() {\n        return null;\n    }\n\n    @Override\n    public void overridePendingTransition(int enterAnim, int exitAnim) {\n        //如果使用的资源不是系统资源，我们无法支持这个特性。\n        if ((enterAnim & 0xFF000000) != 0x01000000) {\n            enterAnim = 0;\n        }\n        if ((exitAnim & 0xFF000000) != 0x01000000) {\n            exitAnim = 0;\n        }\n        hostActivityDelegator.overridePendingTransition(enterAnim, exitAnim);\n    }\n\n\n    @Override\n    public void startActivityForResult(Intent intent, int requestCode) {\n        startActivityForResult(intent, requestCode, null);\n    }\n\n    @Override\n    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {\n        final Intent pluginIntent = new Intent(intent);\n        pluginIntent.setExtrasClassLoader(mPluginClassLoader);\n        ComponentName callingActivity = new ComponentName(getPackageName(), getClass().getName());\n        final boolean success = mPluginComponentLauncher.startActivityForResult(hostActivityDelegator, pluginIntent, requestCode, options, callingActivity);\n        if (!success) {\n            hostActivityDelegator.startActivityForResult(intent, requestCode, options);\n        }\n    }\n\n\n    @Override\n    public SharedPreferences getPreferences(int mode) {\n        return super.getSharedPreferences(getLocalClassName(), mode);\n    }\n\n    @Override\n    public String getLocalClassName() {\n        return this.getClass().getName();\n    }\n\n\n    @Override\n    public boolean shouldUpRecreateTask(Intent targetIntent) {\n        Intent intent = mPluginComponentLauncher.convertPluginActivityIntent(targetIntent);\n        return hostActivityDelegator.shouldUpRecreateTask(intent);\n    }\n\n    @Override\n    public boolean navigateUpTo(Intent upIntent) {\n        Intent intent = mPluginComponentLauncher.convertPluginActivityIntent(upIntent);\n        return hostActivityDelegator.navigateUpTo(intent);\n    }\n\n    @Override\n    public final <T extends View> T requireViewById(int id) {\n        T view = findViewById(id);\n        if (view == null) {\n            throw new IllegalArgumentException(\"ID does not reference a View inside this Activity\");\n        }\n        return view;\n    }\n\n    @Override\n    public void startIntentSenderFromChild(ShadowActivity arg0, IntentSender arg1, int arg2, Intent arg3, int arg4, int arg5, int arg6) throws IntentSender.SendIntentException {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public void startIntentSenderFromChild(ShadowActivity arg0, IntentSender arg1, int arg2, Intent arg3, int arg4, int arg5, int arg6, Bundle arg7) throws IntentSender.SendIntentException {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public boolean navigateUpToFromChild(ShadowActivity arg0, Intent arg1) {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public void finishFromChild(ShadowActivity arg0) {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public void finishActivityFromChild(ShadowActivity arg0, int arg1) {\n        throw new UnsupportedOperationException(\"Unsupported Yet\");\n    }\n\n    @Override\n    public ComponentName getCallingActivity() {\n        return mCallingActivity;\n    }\n\n    /**\n     * https://developer.android.com/reference/android/app/Activity#startActivityFromChild(android.app.Activity,%20android.content.Intent,%20int,%20android.os.Bundle)\n     * <p>\n     * This method was deprecated in API level R.\n     * Use androidx.fragment.app.FragmentActivity#startActivityFromFragment( androidx.fragment.app.Fragment,Intent,int,Bundle)\n     * <p>\n     * 不计划支持这个方法了。\n     */\n    @Override\n    public void startActivityFromChild(ShadowActivity arg0, Intent arg1, int arg2) {\n        throw new UnsupportedOperationException(\"Unsupported\");\n    }\n\n    /**\n     * https://developer.android.com/reference/android/app/Activity#startActivityFromChild(android.app.Activity,%20android.content.Intent,%20int,%20android.os.Bundle)\n     * <p>\n     * This method was deprecated in API level R.\n     * Use androidx.fragment.app.FragmentActivity#startActivityFromFragment( androidx.fragment.app.Fragment,Intent,int,Bundle)\n     * <p>\n     * 不计划支持这个方法了。\n     */\n    @Override\n    public void startActivityFromChild(ShadowActivity arg0, Intent arg1, int arg2, Bundle arg3) {\n        throw new UnsupportedOperationException(\"Unsupported\");\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowActivityLifecycleCallbacks.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.container.HostActivityDelegator;\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\nimport java.lang.ref.WeakReference;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.WeakHashMap;\n\npublic interface ShadowActivityLifecycleCallbacks {\n\n    void onActivityPreCreated(ShadowActivity activity, Bundle savedInstanceState);\n\n    void onActivityCreated(ShadowActivity activity, Bundle savedInstanceState);\n\n    void onActivityPostCreated(ShadowActivity activity, Bundle savedInstanceState);\n\n\n    void onActivityPreStarted(ShadowActivity activity);\n\n\n    void onActivityStarted(ShadowActivity activity);\n\n\n    void onActivityPostStarted(ShadowActivity activity);\n\n\n    void onActivityPreResumed(ShadowActivity activity);\n\n\n    void onActivityResumed(ShadowActivity activity);\n\n\n    void onActivityPostResumed(ShadowActivity activity);\n\n\n    void onActivityPrePaused(ShadowActivity activity);\n\n\n    void onActivityPaused(ShadowActivity activity);\n\n\n    void onActivityPostPaused(ShadowActivity activity);\n\n\n    void onActivityPreStopped(ShadowActivity activity);\n\n\n    void onActivityStopped(ShadowActivity activity);\n\n\n    void onActivityPostStopped(ShadowActivity activity);\n\n\n    void onActivityPreSaveInstanceState(ShadowActivity activity, Bundle outState);\n\n\n    void onActivitySaveInstanceState(ShadowActivity activity, Bundle outState);\n\n\n    void onActivityPostSaveInstanceState(ShadowActivity activity, Bundle outState);\n\n\n    void onActivityPreDestroyed(ShadowActivity activity);\n\n\n    void onActivityDestroyed(ShadowActivity activity);\n\n\n    void onActivityPostDestroyed(ShadowActivity activity);\n\n    class Wrapper implements Application.ActivityLifecycleCallbacks {\n\n        final ShadowActivityLifecycleCallbacks shadowActivityLifecycleCallbacks;\n        final Object runtimeObject;\n        private boolean isRegistered;\n\n        public Wrapper(ShadowActivityLifecycleCallbacks shadowActivityLifecycleCallbacks, Object runtimeObject) {\n            this.shadowActivityLifecycleCallbacks = shadowActivityLifecycleCallbacks;\n            this.runtimeObject = runtimeObject;\n        }\n\n        private ShadowActivity getPluginActivity(Activity activity) {\n            if (activity instanceof PluginContainerActivity) {\n                return (ShadowActivity) PluginActivity.get((PluginContainerActivity) activity);\n            } else {\n                return null;\n            }\n        }\n\n        @Override\n        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityCreated(pluginActivity, savedInstanceState);\n            }\n        }\n\n        @Override\n        public void onActivityStarted(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityStarted(pluginActivity);\n            }\n        }\n\n        @Override\n        public void onActivityResumed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityResumed(pluginActivity);\n            }\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityPaused(pluginActivity);\n            }\n        }\n\n        @Override\n        public void onActivityStopped(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityStopped(pluginActivity);\n            }\n        }\n\n        @Override\n        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivitySaveInstanceState(pluginActivity, outState);\n            }\n        }\n\n        @Override\n        public void onActivityDestroyed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                shadowActivityLifecycleCallbacks.onActivityDestroyed(pluginActivity);\n            }\n        }\n\n        @Override\n        public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {\n            //此时PluginActivity尚未构造。改由onPluginActivityPreCreated通知。\n        }\n\n        public void onPluginActivityPreCreated(ShadowActivity pluginActivity, Bundle savedInstanceState) {\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreCreated(pluginActivity, savedInstanceState);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostCreated(pluginActivity, savedInstanceState);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPreStarted(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreStarted(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostStarted(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostStarted(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPreResumed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreResumed(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostResumed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostResumed(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPrePaused(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPrePaused(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostPaused(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostPaused(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPreStopped(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreStopped(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostStopped(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostStopped(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPreSaveInstanceState(Activity activity, Bundle outState) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreSaveInstanceState(pluginActivity, outState);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostSaveInstanceState(Activity activity, Bundle outState) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostSaveInstanceState(pluginActivity, outState);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPreDestroyed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPreDestroyed(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        @Override\n        public void onActivityPostDestroyed(Activity activity) {\n            final ShadowActivity pluginActivity = getPluginActivity(activity);\n            if (checkOwnerActivity(pluginActivity)) {\n                try {\n                    shadowActivityLifecycleCallbacks.onActivityPostDestroyed(pluginActivity);\n                } catch (AbstractMethodError ignored) {\n                    //兼容Java8接口default方法\n                }\n            }\n        }\n\n        /**\n         * 检测Activity是否属于当前Application所在的插件\n         *\n         * @param activity 插件Activity\n         * @return 是否属于当前Application所在的插件 true属于\n         */\n        private boolean checkOwnerActivity(PluginActivity activity) {\n            if (activity == null) {\n                return false;\n            } else if (runtimeObject instanceof ShadowApplication) {\n                return activity.mPluginApplication == runtimeObject;\n            } else {\n                return activity == runtimeObject;\n            }\n        }\n    }\n\n    class Holder {\n        final private Map<ShadowActivityLifecycleCallbacks,\n                WeakReference<Wrapper>>\n                mShadowActivityLifecycleCallbacksWrapperMap = new WeakHashMap<>();\n\n        /**\n         * 针对业务代码自己不持有ActivityLifecycleCallbacks的情况，\n         * 无法通过mShadowActivityLifecycleCallbacksWrapperMap获取所有wrapper，\n         * 需要特别记录所有wrapper。\n         * <p>\n         * 采用弱引用以便不影响Wrapper原本的GC时机，Wrapper至少被系统持有。\n         * <p>\n         * GuardedBy mShadowActivityLifecycleCallbacksWrapperMap\n         */\n        final private Map<ShadowActivityLifecycleCallbacks.Wrapper, Object>\n                mAllShadowActivityLifecycleCallbackWrappers = new WeakHashMap<>();\n\n        public void notifyPluginActivityPreCreated(ShadowActivity pluginActivity,\n                                                   Bundle savedInstanceState) {\n            synchronized (mShadowActivityLifecycleCallbacksWrapperMap) {\n                //onPluginActivityPreCreated中可能会再次调用registerActivityLifecycleCallbacks，\n                //进而修改mAllShadowActivityLifecycleCallbackWrappers，\n                //因此需要先复制出待通知的callback，再通知。\n                List<Wrapper> copiedWrappers = new LinkedList<>();\n                Set<Wrapper> wrappers\n                        = mAllShadowActivityLifecycleCallbackWrappers.keySet();\n                for (ShadowActivityLifecycleCallbacks.Wrapper wrapper : wrappers) {\n                    // wrapper是弱引用持有的，需要二次验证其是否处于register状态\n                    if (wrapper != null && wrapper.isRegistered) {\n                        copiedWrappers.add(wrapper);\n                    }\n                }\n\n                for (ShadowActivityLifecycleCallbacks.Wrapper wrapper : copiedWrappers) {\n                    wrapper.onPluginActivityPreCreated(pluginActivity, savedInstanceState);\n                }\n            }\n        }\n\n        private ShadowActivityLifecycleCallbacks.Wrapper shadowActivityLifecycleCallbacksToWrapper(\n                ShadowActivityLifecycleCallbacks callbacks,\n                Object caller\n        ) {\n            if (callbacks == null) {\n                return null;\n            }\n            synchronized (mShadowActivityLifecycleCallbacksWrapperMap) {\n                ShadowActivityLifecycleCallbacks.Wrapper wrapper;\n                WeakReference<ShadowActivityLifecycleCallbacks.Wrapper> weakReference\n                        = mShadowActivityLifecycleCallbacksWrapperMap.get(callbacks);\n                wrapper = weakReference == null ? null : weakReference.get();\n                if (wrapper == null) {\n                    wrapper = new ShadowActivityLifecycleCallbacks.Wrapper(callbacks, caller);\n                    mShadowActivityLifecycleCallbacksWrapperMap.put(callbacks,\n                            new WeakReference<>(wrapper));\n                    mAllShadowActivityLifecycleCallbackWrappers.put(wrapper, null);\n                }\n                return wrapper;\n            }\n        }\n\n        void registerActivityLifecycleCallbacks(ShadowActivityLifecycleCallbacks callback,\n                                                Object caller,\n                                                Object hostActivityDelegatorOrApplication) {\n            Wrapper wrapper = shadowActivityLifecycleCallbacksToWrapper(callback, caller);\n            wrapper.isRegistered = true;\n            if (hostActivityDelegatorOrApplication instanceof HostActivityDelegator) {\n                ((HostActivityDelegator) hostActivityDelegatorOrApplication)\n                        .registerActivityLifecycleCallbacks(wrapper);\n            } else {\n                ((Application) hostActivityDelegatorOrApplication)\n                        .registerActivityLifecycleCallbacks(wrapper);\n            }\n        }\n\n        void unregisterActivityLifecycleCallbacks(ShadowActivityLifecycleCallbacks callback,\n                                                  Object caller,\n                                                  Object hostActivityDelegatorOrApplication) {\n            Wrapper wrapper = shadowActivityLifecycleCallbacksToWrapper(callback, caller);\n            wrapper.isRegistered = false;\n            if (hostActivityDelegatorOrApplication instanceof HostActivityDelegator) {\n                ((HostActivityDelegator) hostActivityDelegatorOrApplication)\n                        .unregisterActivityLifecycleCallbacks(wrapper);\n            } else {\n                ((Application) hostActivityDelegatorOrApplication)\n                        .unregisterActivityLifecycleCallbacks(wrapper);\n            }\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowAppComponentFactory.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.content.BroadcastReceiver;\nimport android.content.ContentProvider;\nimport android.content.Intent;\n\npublic class ShadowAppComponentFactory {\n\n    public ShadowApplication instantiateApplication(ClassLoader cl,\n                                                    String className)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ShadowApplication) cl.loadClass(className).newInstance();\n    }\n\n    public ShadowActivity instantiateActivity(ClassLoader cl, String className,\n                                              Intent intent)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ShadowActivity) cl.loadClass(className).newInstance();\n    }\n\n    public BroadcastReceiver instantiateReceiver(ClassLoader cl,\n                                                 String className, Intent intent)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (BroadcastReceiver) cl.loadClass(className).newInstance();\n    }\n\n    public ShadowService instantiateService(ClassLoader cl,\n                                            String className, Intent intent)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ShadowService) cl.loadClass(className).newInstance();\n    }\n\n    public ContentProvider instantiateProvider(ClassLoader cl,\n                                               String className)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ContentProvider) cl.loadClass(className).newInstance();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Application;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentCallbacks;\nimport android.content.ComponentCallbacks2;\nimport android.content.Context;\nimport android.content.IntentFilter;\nimport android.content.res.Configuration;\nimport android.os.Build;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 用于在plugin-loader中调用假的Application方法的接口\n */\npublic class ShadowApplication extends ShadowContext {\n\n    private Application mHostApplication;\n\n    private Map<String, String[]> mBroadcasts;\n\n    private ShadowAppComponentFactory mAppComponentFactory;\n\n    final public ShadowActivityLifecycleCallbacks.Holder mActivityLifecycleCallbacksHolder\n            = new ShadowActivityLifecycleCallbacks.Holder();\n\n    public boolean isCallOnCreate;\n\n    @Override\n    public Context getApplicationContext() {\n        return this;\n    }\n\n    public void registerActivityLifecycleCallbacks(\n            ShadowActivityLifecycleCallbacks callback) {\n        mActivityLifecycleCallbacksHolder.registerActivityLifecycleCallbacks(\n                callback, this, mHostApplication\n        );\n    }\n\n    public void unregisterActivityLifecycleCallbacks(\n            ShadowActivityLifecycleCallbacks callback) {\n        mActivityLifecycleCallbacksHolder.unregisterActivityLifecycleCallbacks(\n                callback, this, mHostApplication\n        );\n    }\n\n    public void onCreate() {\n\n        isCallOnCreate = true;\n\n        for (Map.Entry<String, String[]> entry : mBroadcasts.entrySet()) {\n            try {\n                String receiverClassname = entry.getKey();\n                BroadcastReceiver receiver = mAppComponentFactory.instantiateReceiver(\n                        mPluginClassLoader,\n                        receiverClassname,\n                        null);\n\n                IntentFilter intentFilter = new IntentFilter();\n                String[] receiverActions = entry.getValue();\n                if (receiverActions != null) {\n                    for (String action : receiverActions) {\n                        intentFilter.addAction(action);\n                    }\n                }\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                    registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED);\n                } else {\n                    registerReceiver(receiver, intentFilter);\n                }\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n        mHostApplication.registerComponentCallbacks(new ComponentCallbacks2() {\n            @Override\n            public void onTrimMemory(int level) {\n                ShadowApplication.this.onTrimMemory(level);\n            }\n\n            @Override\n            public void onConfigurationChanged(Configuration newConfig) {\n                ShadowApplication.this.onConfigurationChanged(newConfig);\n            }\n\n            @Override\n            public void onLowMemory() {\n                ShadowApplication.this.onLowMemory();\n            }\n        });\n    }\n\n\n    public void onTerminate() {\n        throw new UnsupportedOperationException();\n    }\n\n\n    public void onConfigurationChanged(Configuration newConfig) {\n        //do nothing.\n    }\n\n\n    public void onLowMemory() {\n        //do nothing.\n    }\n\n\n    public void onTrimMemory(int level) {\n        //do nothing.\n    }\n\n\n    public void registerComponentCallbacks(ComponentCallbacks callback) {\n        mHostApplication.registerComponentCallbacks(callback);\n    }\n\n\n    public void unregisterComponentCallbacks(ComponentCallbacks callback) {\n        mHostApplication.unregisterComponentCallbacks(callback);\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public void registerOnProvideAssistDataListener(Application.OnProvideAssistDataListener callback) {\n        mHostApplication.registerOnProvideAssistDataListener(callback);\n    }\n\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public void unregisterOnProvideAssistDataListener(Application.OnProvideAssistDataListener callback) {\n        mHostApplication.unregisterOnProvideAssistDataListener(callback);\n    }\n\n    public void setHostApplicationContextAsBase(Context hostAppContext) {\n        super.attachBaseContext(hostAppContext);\n        mHostApplication = (Application) hostAppContext;\n    }\n\n    public void setBroadcasts(PluginManifest.ReceiverInfo[] receiverInfos) {\n        Map<String, String[]> classNameToActions = new HashMap<>();\n        if (receiverInfos != null) {\n            for (PluginManifest.ReceiverInfo receiverInfo : receiverInfos) {\n                classNameToActions.put(receiverInfo.className, receiverInfo.actions);\n            }\n        }\n        mBroadcasts = classNameToActions;\n    }\n\n    public void attachBaseContext(Context base) {\n        //do nothing.\n    }\n\n    public void setAppComponentFactory(ShadowAppComponentFactory factory) {\n        mAppComponentFactory = factory;\n    }\n\n    @SuppressLint(\"NewApi\")\n    public static String getProcessName() {\n        return Application.getProcessName();\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowContext.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\n\nimport com.tencent.shadow.core.runtime.container.GeneratedHostActivityDelegator;\n\npublic class ShadowContext extends SubDirContextThemeWrapper {\n    PluginComponentLauncher mPluginComponentLauncher;\n    ClassLoader mPluginClassLoader;\n    ShadowApplication mShadowApplication;\n    Resources mPluginResources;\n    LayoutInflater mLayoutInflater;\n    ApplicationInfo mApplicationInfo;\n    protected String mPartKey;\n    private String mBusinessName;\n\n    public ShadowContext() {\n    }\n\n    public ShadowContext(Context base, int themeResId) {\n        super(base, themeResId);\n    }\n\n    public final void setPluginResources(Resources resources) {\n        mPluginResources = resources;\n    }\n\n    public final void setPluginClassLoader(ClassLoader classLoader) {\n        mPluginClassLoader = classLoader;\n    }\n\n    public void setPluginComponentLauncher(PluginComponentLauncher pluginComponentLauncher) {\n        mPluginComponentLauncher = pluginComponentLauncher;\n    }\n\n    public void setShadowApplication(ShadowApplication shadowApplication) {\n        mShadowApplication = shadowApplication;\n    }\n\n    public void setApplicationInfo(ApplicationInfo applicationInfo) {\n        ApplicationInfo copy = new ApplicationInfo(applicationInfo);\n        copy.metaData = null;//正常通过Context获得的ApplicationInfo就没有metaData\n        mApplicationInfo = copy;\n    }\n\n    public void setBusinessName(String businessName) {\n        if (TextUtils.isEmpty(businessName)) {\n            businessName = null;\n        }\n        this.mBusinessName = businessName;\n    }\n\n    public void setPluginPartKey(String partKey) {\n        this.mPartKey = partKey;\n    }\n\n    @Override\n    public Context getApplicationContext() {\n        return mShadowApplication;\n    }\n\n    @Override\n    public Resources getResources() {\n        return mPluginResources;\n    }\n\n    @Override\n    public AssetManager getAssets() {\n        return mPluginResources.getAssets();\n    }\n\n    @Override\n    public Object getSystemService(String name) {\n        if (LAYOUT_INFLATER_SERVICE.equals(name)) {\n            if (mLayoutInflater == null) {\n                LayoutInflater inflater = (LayoutInflater) super.getSystemService(name);\n                mLayoutInflater = ShadowLayoutInflater.build(inflater, this, mPartKey);\n            }\n            return mLayoutInflater;\n        }\n        return super.getSystemService(name);\n    }\n\n    @Override\n    public ClassLoader getClassLoader() {\n        return mPluginClassLoader;\n    }\n\n    public interface PluginComponentLauncher {\n        /**\n         * 启动Activity\n         *\n         * @param shadowContext 启动context\n         * @param intent        插件内传来的Intent.\n         * @return <code>true</code>表示该Intent是为了启动插件内Activity的,已经被正确消费了.\n         * <code>false</code>表示该Intent不是插件内的Activity.\n         */\n        boolean startActivity(ShadowContext shadowContext, Intent intent, Bundle options);\n\n        /**\n         * 启动Activity\n         *\n         * @param delegator       发起启动的activity的delegator\n         * @param intent          插件内传来的Intent.\n         * @param callingActivity 调用者\n         * @return <code>true</code>表示该Intent是为了启动插件内Activity的,已经被正确消费了.\n         * <code>false</code>表示该Intent不是插件内的Activity.\n         */\n        boolean startActivityForResult(GeneratedHostActivityDelegator delegator, Intent intent, int requestCode, Bundle option, ComponentName callingActivity);\n\n        Pair<Boolean, ComponentName> startService(ShadowContext context, Intent service);\n\n        Pair<Boolean, Boolean> stopService(ShadowContext context, Intent name);\n\n        Pair<Boolean, Boolean> bindService(ShadowContext context, Intent service, ServiceConnection conn, int flags);\n\n        Pair<Boolean, ?> unbindService(ShadowContext context, ServiceConnection conn);\n\n        Intent convertPluginActivityIntent(Intent pluginIntent);\n\n    }\n\n    @Override\n    public void startActivity(Intent intent) {\n        startActivity(intent, null);\n    }\n\n    @Override\n    public void startActivity(Intent intent, Bundle options) {\n        final Intent pluginIntent = new Intent(intent);\n        pluginIntent.setExtrasClassLoader(mPluginClassLoader);\n        final boolean success = mPluginComponentLauncher.startActivity(this, pluginIntent, options);\n        if (!success) {\n            super.startActivity(intent, options);\n        }\n    }\n\n    @android.annotation.TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    public void superStartActivity(Intent intent, Bundle options) {\n        super.startActivity(intent, options);\n    }\n\n    @Override\n    public void unbindService(ServiceConnection conn) {\n        if (!mPluginComponentLauncher.unbindService(this, conn).first)\n            super.unbindService(conn);\n    }\n\n    @Override\n    public boolean bindService(Intent service, ServiceConnection conn, int flags) {\n        if (service.getComponent() == null) {\n            return super.bindService(service, conn, flags);\n        }\n        Pair<Boolean, Boolean> ret = mPluginComponentLauncher.bindService(this, service, conn, flags);\n        if (!ret.first)\n            return super.bindService(service, conn, flags);\n        return ret.second;\n    }\n\n    @Override\n    public boolean stopService(Intent name) {\n        if (name.getComponent() == null) {\n            return super.stopService(name);\n        }\n        Pair<Boolean, Boolean> ret = mPluginComponentLauncher.stopService(this, name);\n        if (!ret.first)\n            return super.stopService(name);\n        return ret.second;\n    }\n\n    @Override\n    public ComponentName startService(Intent service) {\n        if (service.getComponent() == null) {\n            return super.startService(service);\n        }\n        Pair<Boolean, ComponentName> ret = mPluginComponentLauncher.startService(this, service);\n        if (!ret.first)\n            return super.startService(service);\n        return ret.second;\n    }\n\n    @Override\n    public ApplicationInfo getApplicationInfo() {\n        return mApplicationInfo;\n    }\n\n    public PluginComponentLauncher getPendingIntentConverter() {\n        return mPluginComponentLauncher;\n    }\n\n    @Override\n    String getSubDirName() {\n        if (mBusinessName == null) {\n            return null;\n        } else {\n            return \"ShadowPlugin_\" + mBusinessName;\n        }\n    }\n\n    @Override\n    public String getPackageName() {\n        return mApplicationInfo.packageName;\n    }\n\n    @Override\n    public String getPackageCodePath() {\n        PluginPartInfo pluginInfo = PluginPartInfoManager.getPluginInfo(getClassLoader());\n        return pluginInfo.packageManager.getArchiveFilePath();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowDialogSupport.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.app.Activity;\nimport android.app.Dialog;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class ShadowDialogSupport {\n\n    public static void dialogSetOwnerActivity(Dialog dialog, ShadowActivity activity) {\n        Activity hostActivity = (Activity) activity.hostActivityDelegator.getHostActivity();\n        dialog.setOwnerActivity(hostActivity);\n    }\n\n    public static ShadowActivity dialogGetOwnerActivity(Dialog dialog) {\n        PluginContainerActivity ownerActivity = (PluginContainerActivity) dialog.getOwnerActivity();\n        if (ownerActivity != null) {\n            return (ShadowActivity) PluginActivity.get(ownerActivity);\n        } else {\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowFactory2.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewStub;\n\nimport java.lang.reflect.Constructor;\nimport java.util.HashMap;\n\n/**\n * 具备创建自定义View功能的Factory2\n * <p>\n * // TODO #36 实现LayoutInflater 的 filter功能\n */\npublic class ShadowFactory2 implements LayoutInflater.Factory2 {\n\n    private String mPartKey;\n\n    private final Object[] mConstructorArgs = new Object[2];\n\n    private final Class<?>[] mConstructorSignature = new Class[]{\n            Context.class, AttributeSet.class};\n\n    private final static HashMap<String, Constructor<? extends View>> sConstructorMap =\n            new HashMap<String, Constructor<? extends View>>();\n\n\n    private LayoutInflater mLayoutInflater;\n\n\n    private static final HashMap<String, String> sCreateSystemMap = new HashMap<>();\n\n\n    public ShadowFactory2(String partKey, LayoutInflater layoutInflater) {\n        mPartKey = partKey;\n        mLayoutInflater = layoutInflater;\n    }\n\n    @Override\n    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {\n        View view;\n        if (name.contains(\".\")) {//自定义view\n            if (sCreateSystemMap.get(name) == null) {\n                sCreateSystemMap.put(name, mPartKey);\n            }\n            try {\n                view = createCustomView(name, context, attrs);\n            } catch (Exception e) {\n                view = null;\n            }\n        } else {\n            if (context instanceof PluginActivity) {//fragment的构造在activity中\n                view = ((PluginActivity) context).onCreateView(parent, name, context, attrs);\n            } else {\n                view = null;\n            }\n        }\n        return view;\n    }\n\n    @Override\n    public View onCreateView(String name, Context context, AttributeSet attrs) {\n        return null;\n    }\n\n\n    private View createCustomView(String name, Context context, AttributeSet attrs) {\n        String cacheKey = mPartKey + name;\n        Constructor<? extends View> constructor = sConstructorMap.get(cacheKey);\n        if (constructor != null && !verifyClassLoader(context, constructor)) {\n            constructor = null;\n            sConstructorMap.remove(cacheKey);\n        }\n        Class<? extends View> clazz = null;\n\n        try {\n            if (constructor == null) {\n                // Class not found in the cache, see if it's real, and try to add it\n                clazz = context.getClassLoader().loadClass(name).asSubclass(View.class);\n\n                constructor = clazz.getConstructor(mConstructorSignature);\n                constructor.setAccessible(true);\n                sConstructorMap.put(cacheKey, constructor);\n            }\n\n            Object lastContext = mConstructorArgs[0];\n            if (mConstructorArgs[0] == null) {\n                // Fill in the context if not already within inflation.\n                mConstructorArgs[0] = context;\n            }\n            Object[] args = mConstructorArgs;\n            args[1] = attrs;\n\n            final View view = constructor.newInstance(args);\n            if (view instanceof ViewStub && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n                // Use the same context when inflating ViewStub later.\n                final ViewStub viewStub = (ViewStub) view;\n                viewStub.setLayoutInflater(mLayoutInflater.cloneInContext((Context) args[0]));\n            }\n            mConstructorArgs[0] = lastContext;\n            return view;\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n\n    private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();\n\n    private final boolean verifyClassLoader(Context context, Constructor<? extends View> constructor) {\n        final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();\n        if (constructorLoader == BOOT_CLASS_LOADER) {\n            // fast path for boot class loader (most common case?) - always ok\n            return true;\n        }\n        // in all normal cases (no dynamic code loading), we will exit the following loop on the\n        // first iteration (i.e. when the declaring classloader is the contexts class loader).\n        ClassLoader cl = context.getClassLoader();\n        do {\n            if (constructorLoader == cl) {\n                return true;\n            }\n            cl = cl.getParent();\n        } while (cl != null);\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.annotation.SuppressLint;\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"NewApi\")\npublic class ShadowFragmentSupport {\n\n    public static ShadowActivity fragmentGetActivity(Fragment fragment) {\n        PluginContainerActivity pluginContainerActivity\n                = (PluginContainerActivity) fragment.getActivity();\n        // When a fragment is not attached or has already been detached, \n        // it needs to behave like Fragment.getActivity(), return null.\n        if (pluginContainerActivity == null) {\n            return null;\n        }\n        return (ShadowActivity) PluginActivity.get(pluginContainerActivity);\n    }\n\n    public static Context fragmentGetContext(Fragment fragment) {\n        Context context = fragment.getContext();\n        if (context instanceof PluginContainerActivity) {\n            return PluginActivity.get((PluginContainerActivity) context);\n        } else {\n            return context;\n        }\n    }\n\n    public static Object fragmentGetHost(Fragment fragment) {\n        Object host = fragment.getHost();\n        if (host instanceof PluginContainerActivity) {\n            return PluginActivity.get((PluginContainerActivity) host);\n        } else {\n            return host;\n        }\n    }\n\n    public static void fragmentStartActivity(Fragment fragment, Intent intent) {\n        fragmentStartActivity(fragment, intent, null);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public static void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) {\n        ShadowContext shadowContext = fragmentGetActivity(fragment);\n        Intent containerActivityIntent\n                = shadowContext.mPluginComponentLauncher.convertPluginActivityIntent(intent);\n        if (options == null) {\n            fragment.startActivity(containerActivityIntent);\n        } else {\n            fragment.startActivity(containerActivityIntent, options);\n        }\n    }\n\n    public static void fragmentStartActivityForResult(Fragment fragment, Intent intent, int requestCode) {\n        fragmentStartActivityForResult(fragment, intent, requestCode, null);\n    }\n\n    public static void fragmentStartActivityForResult(Fragment fragment, Intent intent, int requestCode, Bundle options) {\n        ShadowContext shadowContext = fragmentGetActivity(fragment);\n        Intent containerActivityIntent\n                = shadowContext.mPluginComponentLauncher.convertPluginActivityIntent(intent);\n        if (options == null) {\n            fragment.startActivityForResult(containerActivityIntent, requestCode);\n        } else {\n            fragment.startActivityForResult(containerActivityIntent, requestCode, options);\n        }\n    }\n\n    public static Context toPluginContext(Context pluginContainerActivity) {\n        return PluginActivity.get((PluginContainerActivity) pluginContainerActivity);\n    }\n\n    public static Context toOriginalContext(Context pluginActivity) {\n        PluginActivity activity = (PluginActivity) pluginActivity;\n        return activity.hostActivityDelegator.getHostActivity().getImplementActivity();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowInstrumentation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.app.Instrumentation;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.PersistableBundle;\n\npublic class ShadowInstrumentation extends Instrumentation {\n\n    public void callActivityOnDestroy(ShadowActivity activity) {\n        Activity hostActivity = (Activity) activity.hostActivityDelegator.getHostActivity();\n        super.callActivityOnDestroy(hostActivity);\n    }\n\n    static public ShadowApplication newShadowApplication(Class<?> clazz, Context context)\n            throws InstantiationException, IllegalAccessException,\n            ClassNotFoundException {\n        ShadowApplication app = (ShadowApplication) clazz.newInstance();\n\n        app.attachBaseContext(context);\n\n        //这样构造的 ShadowApplication 跟 CreateApplicationBloc 正常构造的不一样。\n        //这里构造的只是个Context而已，没有插件的各种信息。\n        return app;\n    }\n\n\n    /**\n     * 因为参数签名和newActivity一样,但是返回值不一样,所以无法override\n     * 只能通过transform,让newApplication转移到newShadowApplication上来\n     */\n    public ShadowApplication newShadowApplication(ClassLoader cl, String className, Context context)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        ShadowApplication app = (ShadowApplication) cl.loadClass(className).newInstance();\n        app.attachBaseContext(context);\n        return app;\n    }\n\n    /**\n     * 因为参数签名和newActivity一样,但是返回值不一样,所以无法override\n     * 只能通过transform,让newActivity转移到newShadowActivity上来\n     */\n    public ShadowActivity newShadowActivity(ClassLoader cl, String className, Intent intent)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ShadowActivity) cl.loadClass(className).newInstance();\n    }\n\n    public void callApplicationOnCreate(ShadowApplication app) {\n        app.onCreate();\n    }\n\n    /**\n     * shadow启动插件activity时并不是依靠Instrumentation来操作的,所以这边的execStartActivity并没有什么实在的意义\n     * 而且这个方法里面都大量的UnsupportedAppUsage方法调用,如果重写并不符合shadow零反射的原则\n     * 所以,重写这个方法,但是仅仅返回一个空的ActivityResult只是是作为帮助编译通过的方法\n     * <p>\n     * 像com.didi.virtualapk是用自定义的Instrumentation做了一层代理,替换intent中合适的activity\n     * 但是shadow的activity不是这样启动的,这个方法也不会执行,仅仅保证编译通过,能正常打出插件包,而virtualapk其实是完全失效的\n     */\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, ShadowActivity target, Intent intent, int requestCode) {\n        return new ActivityResult(requestCode, intent);\n    }\n\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, ShadowActivity target, Intent intent, int requestCode, Bundle options) {\n        return new ActivityResult(requestCode, intent);\n    }\n\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {\n        return new ActivityResult(requestCode, intent);\n    }\n\n    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {\n        return new ActivityResult(requestCode, intent);\n    }\n\n    /**\n     * 同execStartActivity方法一样,其实插件中并不会调用到这里\n     * 而且这个方法里面都大量的UnsupportedAppUsage方法调用,如果重写并不符合shadow零反射的原则\n     */\n    public void callActivityOnCreate(ShadowActivity activity, Bundle icicle) {\n    }\n\n    public void callActivityOnCreate(ShadowActivity activity, Bundle icicle, PersistableBundle persistentState) {\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowIntentService.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.content.Intent;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Message;\n\nimport static android.app.Service.START_NOT_STICKY;\nimport static android.app.Service.START_REDELIVER_INTENT;\n\npublic abstract class ShadowIntentService extends ShadowService {\n    private volatile Looper mServiceLooper;\n    private volatile ServiceHandler mServiceHandler;\n    private String mName;\n    private boolean mRedelivery;\n\n    private final class ServiceHandler extends Handler {\n        public ServiceHandler(Looper looper) {\n            super(looper);\n        }\n\n        @Override\n        public void handleMessage(Message msg) {\n            onHandleIntent((Intent) msg.obj);\n            stopSelf(msg.arg1);\n        }\n    }\n\n    public ShadowIntentService(String name) {\n        super();\n        mName = name;\n    }\n\n    public void setIntentRedelivery(boolean enabled) {\n        mRedelivery = enabled;\n    }\n\n    @Override\n    public void onCreate() {\n        // TODO: It would be nice to have an option to hold a partial wakelock\n        // during processing, and to have a static startService(Context, Intent)\n        // method that would launch the service & hand off a wakelock.\n\n        super.onCreate();\n        HandlerThread thread = new HandlerThread(\"IntentService[\" + mName + \"]\");\n        thread.start();\n\n        mServiceLooper = thread.getLooper();\n        mServiceHandler = new ServiceHandler(mServiceLooper);\n    }\n\n    @Override\n    public void onStart(Intent intent, int startId) {\n        Message msg = mServiceHandler.obtainMessage();\n        msg.arg1 = startId;\n        msg.obj = intent;\n        mServiceHandler.sendMessage(msg);\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        onStart(intent, startId);\n        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;\n    }\n\n    @Override\n    public void onDestroy() {\n        mServiceLooper.quit();\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    protected abstract void onHandleIntent(Intent intent);\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowLayoutInflater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\n\n\n/**\n * 本类主要有2个目的\n * 1.替换xml里面的WebView为ShadowWebView\n * 2.给插件自定义View加上特定的前缀，防止插件切换的时候由于多插件自定义view重名，LayoutInflater缓存类构造器导致view冲突\n */\npublic class ShadowLayoutInflater extends ShadowWebViewLayoutInflater {\n\n    private Factory mOriginalFactory = null;\n    private Factory2 mOriginalFactory2 = null;\n\n    @Override\n    public void setFactory(Factory factory) {\n        mOriginalFactory = factory;\n        super.setFactory(factory);\n    }\n\n    @Override\n    public void setFactory2(Factory2 factory) {\n        mOriginalFactory = mOriginalFactory2 = factory;\n        super.setFactory2(factory);\n    }\n\n    public static Factory getOriginalFactory(LayoutInflater inflater) {\n        if (inflater instanceof ShadowLayoutInflater) {\n            return ((ShadowLayoutInflater) inflater).mOriginalFactory;\n        } else {\n            return inflater.getFactory();\n        }\n    }\n\n    public static Factory2 getOriginalFactory2(LayoutInflater inflater) {\n        if (inflater instanceof ShadowLayoutInflater) {\n            return ((ShadowLayoutInflater) inflater).mOriginalFactory2;\n        } else {\n            return inflater.getFactory2();\n        }\n    }\n\n    public static ShadowLayoutInflater build(LayoutInflater original, Context newContext, String partKey) {\n        InnerInflater innerLayoutInflater = new InnerInflater(original, newContext, partKey);\n        return new ShadowLayoutInflater(innerLayoutInflater, newContext, partKey);\n    }\n\n    private static class InnerInflater extends ShadowLayoutInflater {\n        private InnerInflater(LayoutInflater original, Context newContext, String partKey) {\n            super(original, newContext, partKey);\n            setFactory2(new ShadowFactory2(partKey, this));\n        }\n    }\n\n    private ShadowLayoutInflater(LayoutInflater original, Context newContext, String partKey) {\n        super(original, newContext);\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowNativeActivity.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.view.InputQueue;\nimport android.view.SurfaceHolder;\nimport android.view.ViewTreeObserver;\n\nimport com.tencent.shadow.core.runtime.container.HostActivityDelegator;\nimport com.tencent.shadow.core.runtime.container.HostNativeActivityDelegator;\n\npublic class ShadowNativeActivity extends ShadowActivity implements SurfaceHolder.Callback2,\n        InputQueue.Callback, ViewTreeObserver.OnGlobalLayoutListener {\n\n    private HostNativeActivityDelegator hostNativeActivityDelegator;\n\n    @Override\n    public void setHostActivityDelegator(HostActivityDelegator delegator) {\n        super.setHostActivityDelegator(delegator);\n        hostNativeActivityDelegator = (HostNativeActivityDelegator) delegator;\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        hostNativeActivityDelegator.superSurfaceCreated(holder);\n    }\n\n    @Override\n    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {\n        hostNativeActivityDelegator.superSurfaceChanged(holder, format, width, height);\n    }\n\n    @Override\n    public void surfaceRedrawNeeded(SurfaceHolder holder) {\n        hostNativeActivityDelegator.superSurfaceRedrawNeeded(holder);\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        hostNativeActivityDelegator.superSurfaceDestroyed(holder);\n    }\n\n    @Override\n    public void onInputQueueCreated(InputQueue queue) {\n        hostNativeActivityDelegator.superOnInputQueueCreated(queue);\n    }\n\n    @Override\n    public void onInputQueueDestroyed(InputQueue queue) {\n        hostNativeActivityDelegator.superOnInputQueueDestroyed(queue);\n    }\n\n    @Override\n    public void onGlobalLayout() {\n        hostNativeActivityDelegator.superOnGlobalLayout();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowPackageItemInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.pm.PackageItemInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Resources;\nimport android.content.res.XmlResourceParser;\n\npublic class ShadowPackageItemInfo {\n\n    /**\n     * @param classLoader     对应插件所在的classLoader\n     * @param packageItemInfo MetaData所在的组件\n     * @param pm              PackageManager\n     * @param name            metaData对应的name\n     * @return 返回所在插件的xml对应的XmlResourceParser\n     */\n    public static XmlResourceParser loadXmlMetaData(ClassLoader classLoader, PackageItemInfo packageItemInfo, PackageManager pm, String name) {\n        PluginPartInfo pluginPartInfo = PluginPartInfoManager.getPluginInfo(classLoader);\n        Resources resources = pluginPartInfo.application.getResources();\n        if (packageItemInfo.metaData != null) {\n            int resid = packageItemInfo.metaData.getInt(name);\n            if (resid != 0) {\n                return resources.getXml(resid);\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowPendingIntent.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\n\n\npublic class ShadowPendingIntent {\n\n    public static PendingIntent getService(Context context, int requestCode,\n                                           Intent intent, int flags) {\n        //todo #51 实现PendingIntent 中的 Service和广播\n        return PendingIntent.getService(context, requestCode, intent, flags);\n    }\n\n    public static PendingIntent getActivity(Context context, int requestCode,\n                                            Intent intent, int flags) {\n        return getActivity(context, requestCode, intent, flags, null);\n    }\n\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n    public static PendingIntent getActivity(Context context, int requestCode,\n                                            Intent intent, int flags, Bundle options) {\n        if (context instanceof ShadowContext && intent.getComponent() != null) {\n            ShadowContext shadowContext = (ShadowContext) context;\n            if (shadowContext.getPendingIntentConverter() != null) {\n                intent = shadowContext.getPendingIntentConverter().convertPluginActivityIntent(intent);\n            }\n            context = shadowContext.getBaseContext();\n        }\n        return PendingIntent.getActivity(context, requestCode, intent, flags, options);\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowService.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.app.Notification;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.Configuration;\nimport android.os.IBinder;\n\nimport java.io.FileDescriptor;\nimport java.io.PrintWriter;\n\n/**\n * Created by tracyluo on 2018/6/5.\n */\npublic abstract class ShadowService extends ShadowContext {\n\n\n    public final void setHostContextAsBase(Context context) {\n        attachBaseContext(context);\n    }\n\n\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        return Service.START_NOT_STICKY;\n    }\n\n    public void onDestroy() {\n\n    }\n\n    public void onConfigurationChanged(Configuration newConfig) {\n\n    }\n\n    public void onLowMemory() {\n\n    }\n\n    public void onTrimMemory(int level) {\n\n    }\n\n    public boolean onUnbind(Intent intent) {\n        return false;\n    }\n\n    public void onTaskRemoved(Intent rootIntent) {\n\n    }\n\n    public void onCreate() {\n\n    }\n\n    public void onRebind(Intent intent) {\n\n    }\n\n    @Deprecated\n    public void onStart(Intent intent, int startId) {\n    }\n\n    @Deprecated\n    public final void setForeground(boolean isForeground) {\n        //todo #37 支持Service设置Foreground\n    }\n\n    public final void startForeground(int id, Notification notification) {\n        //mHostServiceDelegator.startForeground(id, notification);\n        //todo #37 支持Service设置Foreground\n    }\n\n    public final void stopForeground(boolean removeNotification) {\n        //todo #37 支持Service设置Foreground\n        //mHostServiceDelegator.stopForeground(removeNotification);\n    }\n\n    public final void startForeground(int id, Notification notification, int foregroundServiceType) {\n\n    }\n\n    public final void stopForeground(int flags) {\n\n    }\n\n    public final void stopSelf() {\n        stopService(new Intent(this, getClass()));\n    }\n\n    /**\n     * 插件环境下Service不支持调用带参数的stopSelf\n     */\n    public final void stopSelf(int startId) {\n        stopSelf();\n    }\n\n    /**\n     * 插件环境下Service不支持调用带参数的stopSelf\n     */\n    public final boolean stopSelfResult(int startId) {\n        stopSelf();\n        return true;\n    }\n\n    public final ShadowApplication getApplication() {\n        return mShadowApplication;\n    }\n\n    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {\n        writer.println(\"nothing to dump\");\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowWebView.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.net.http.SslError;\nimport android.os.Build;\nimport android.os.Message;\nimport android.util.AttributeSet;\nimport android.view.KeyEvent;\nimport android.webkit.ClientCertRequest;\nimport android.webkit.HttpAuthHandler;\nimport android.webkit.RenderProcessGoneDetail;\nimport android.webkit.SafeBrowsingResponse;\nimport android.webkit.SslErrorHandler;\nimport android.webkit.WebResourceError;\nimport android.webkit.WebResourceRequest;\nimport android.webkit.WebResourceResponse;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\n\nimport java.io.IOException;\n\n/**\n * Created by owenguo on 2018/7/8.\n */\n\npublic class ShadowWebView extends WebView {\n\n    private Context mContext;\n\n    private final String ANDROID_ASSET_PREFIX = \"file:///android_asset/\";\n\n    private final String REPLACE_ASSET_PREFIX = \"http://android.asset/\";\n\n    public ShadowWebView(Context context) {\n        super(context);\n        init(context);\n    }\n\n    public ShadowWebView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context);\n    }\n\n    public ShadowWebView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context);\n    }\n\n    @TargetApi(21)\n    public ShadowWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        init(context);\n    }\n\n    public ShadowWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {\n        super(context, attrs, defStyleAttr, privateBrowsing);\n        init(context);\n    }\n\n    private void init(Context context) {\n        mContext = context;\n        super.setWebViewClient(new WarpWebViewClient(new WebViewClient(), mContext));\n    }\n\n    @Override\n    public void loadUrl(String url) {\n        if (url.startsWith(ANDROID_ASSET_PREFIX)) {\n            url = url.replace(ANDROID_ASSET_PREFIX, REPLACE_ASSET_PREFIX);\n        }\n        super.loadUrl(url);\n    }\n\n    @Override\n    public void setWebViewClient(WebViewClient client) {\n        super.setWebViewClient(new WarpWebViewClient(client, mContext));\n    }\n\n    class WarpWebViewClient extends WebViewClient {\n\n        private WebViewClient mWebViewClient;\n        private Context mContext;\n\n        public WarpWebViewClient(WebViewClient webViewClient, Context context) {\n            mWebViewClient = webViewClient;\n            mContext = context;\n        }\n\n        private WebResourceResponse getInterceptResponse(String url) {\n            if (url.startsWith(REPLACE_ASSET_PREFIX)) {\n                int end = url.indexOf(\"?\");\n                if (end == -1) {\n                    end = url.length();\n                }\n                String filePath = url.substring(REPLACE_ASSET_PREFIX.length(), end);\n                String mime = \"text/html\";\n                if (filePath.contains(\".css\")) {\n                    mime = \"text/css\";\n                } else if (filePath.contains(\".js\")) {\n                    mime = \"application/x-javascript\";\n                } else if (filePath.contains(\".jpg\") || filePath.contains(\".gif\") ||\n                        filePath.contains(\".png\") || filePath.contains(\".jpeg\")) {\n                    mime = \"image/*\";\n                }\n                try {\n                    return new WebResourceResponse(mime, \"utf-8\", mContext.getAssets().open(filePath));\n                } catch (IOException ignored) {\n                }\n            }\n            return null;\n        }\n\n        @Override\n        public boolean shouldOverrideUrlLoading(WebView view, String url) {\n            return mWebViewClient.shouldOverrideUrlLoading(view, url);\n        }\n\n        @TargetApi(Build.VERSION_CODES.N)\n        @Override\n        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\n            return mWebViewClient.shouldOverrideUrlLoading(view, request);\n        }\n\n        @Override\n        public void onPageStarted(WebView view, String url, Bitmap favicon) {\n            mWebViewClient.onPageStarted(view, url, favicon);\n        }\n\n        @Override\n        public void onPageFinished(WebView view, String url) {\n            mWebViewClient.onPageFinished(view, url);\n        }\n\n        @Override\n        public void onLoadResource(WebView view, String url) {\n            mWebViewClient.onLoadResource(view, url);\n        }\n\n        @TargetApi(Build.VERSION_CODES.M)\n        @Override\n        public void onPageCommitVisible(WebView view, String url) {\n            mWebViewClient.onPageCommitVisible(view, url);\n        }\n\n        @Override\n        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {\n            WebResourceResponse resourceResponse = getInterceptResponse(url);\n            if (resourceResponse != null) {\n                return resourceResponse;\n            }\n            return mWebViewClient.shouldInterceptRequest(view, url);\n        }\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {\n            String url = request.getUrl().toString();\n            WebResourceResponse resourceResponse = getInterceptResponse(url);\n            if (resourceResponse != null) {\n                return resourceResponse;\n            }\n            return mWebViewClient.shouldInterceptRequest(view, request);\n        }\n\n        @Override\n        public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) {\n            mWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg);\n        }\n\n        @Override\n        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {\n            mWebViewClient.onReceivedError(view, errorCode, description, failingUrl);\n        }\n\n\n        @TargetApi(Build.VERSION_CODES.M)\n        @Override\n        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {\n            mWebViewClient.onReceivedError(view, request, error);\n        }\n\n\n        @TargetApi(Build.VERSION_CODES.M)\n        @Override\n        public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {\n            mWebViewClient.onReceivedHttpError(view, request, errorResponse);\n        }\n\n        @Override\n        public void onFormResubmission(WebView view, Message dontResend, Message resend) {\n            mWebViewClient.onFormResubmission(view, dontResend, resend);\n        }\n\n        @Override\n        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {\n            mWebViewClient.doUpdateVisitedHistory(view, url, isReload);\n        }\n\n        @Override\n        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {\n            mWebViewClient.onReceivedSslError(view, handler, error);\n        }\n\n\n        @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n        @Override\n        public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {\n            mWebViewClient.onReceivedClientCertRequest(view, request);\n        }\n\n        @Override\n        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {\n            mWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm);\n        }\n\n        @Override\n        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {\n            return mWebViewClient.shouldOverrideKeyEvent(view, event);\n        }\n\n        @Override\n        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {\n            mWebViewClient.onUnhandledKeyEvent(view, event);\n        }\n\n        @Override\n        public void onScaleChanged(WebView view, float oldScale, float newScale) {\n            mWebViewClient.onScaleChanged(view, oldScale, newScale);\n        }\n\n        @Override\n        public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {\n            mWebViewClient.onReceivedLoginRequest(view, realm, account, args);\n        }\n\n\n        @TargetApi(Build.VERSION_CODES.O)\n        @Override\n        public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {\n            return mWebViewClient.onRenderProcessGone(view, detail);\n        }\n\n        @TargetApi(Build.VERSION_CODES.O_MR1)\n        @Override\n        public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {\n            mWebViewClient.onSafeBrowsingHit(view, request, threatType, callback);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowWebViewLayoutInflater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n/**\n * 1.模拟PhoneLayoutInflater的系统view构造过程\n * 2.将xml中的webview替换成shadowWebView\n */\npublic class ShadowWebViewLayoutInflater extends FixedContextLayoutInflater {\n\n    private static final String AndroidWebView = \"android.webkit.WebView\";\n\n    private static final String ShadowPackagePrefix = \"com.tencent.shadow.core.runtime.\";\n\n    private static final String ShadowWebView = \"ShadowWebView\";\n\n    public ShadowWebViewLayoutInflater(LayoutInflater original, Context newContext) {\n        super(original, newContext);\n    }\n\n    @Override\n    LayoutInflater createNewContextLayoutInflater(Context newContext) {\n        if (newContext instanceof PluginContainerActivity) {\n            Object pluginActivity = PluginActivity.get((PluginContainerActivity) newContext);\n            return new ShadowWebViewLayoutInflater(this, (Context) pluginActivity);\n        } else {\n            //context有2种可能，1种是ShadowContext,一种是其他context\n            return new ShadowWebViewLayoutInflater(this, newContext);\n        }\n    }\n\n    @Override\n    Pair<String, String> changeViewNameAndPrefix(String name, String prefix) {\n        if (AndroidWebView.equals(prefix + name)) {\n            prefix = ShadowPackagePrefix;\n            name = ShadowWebView;\n        }\n        return new Pair<>(name, prefix);\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/SubDirContextThemeWrapper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.res.Resources;\nimport android.database.DatabaseErrorHandler;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.os.Build;\nimport android.view.ContextThemeWrapper;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\n\n/**\n * 将Context上所有get*Dir方法都放到原实现的子目录中\n */\nabstract class SubDirContextThemeWrapper extends ContextThemeWrapper {\n    private final Object mSync = new Object();\n\n    /**\n     * GuardedBy(\"mSync\")\n     */\n    private File mDataDir, mFilesDir, mNoBackupFilesDir, mObbDir, mCacheDir, mCodeCacheDir,\n            mExternalCacheDir;\n\n\n    abstract String getSubDirName();\n\n    public SubDirContextThemeWrapper() {\n        super();\n    }\n\n    public SubDirContextThemeWrapper(Context base, int themeResId) {\n        super(base, themeResId);\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    public SubDirContextThemeWrapper(Context base, Resources.Theme theme) {\n        super(base, theme);\n    }\n\n    @Override\n    public File getDataDir() {\n        if (getSubDirName() == null) {\n            return super.getDataDir();\n        }\n        synchronized (mSync) {\n            if (mDataDir == null) {\n                mDataDir = new File(super.getDataDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mDataDir);\n        }\n    }\n\n    @Override\n    public File getFilesDir() {\n        if (getSubDirName() == null) {\n            return super.getFilesDir();\n        }\n        synchronized (mSync) {\n            if (mFilesDir == null) {\n                mFilesDir = new File(super.getFilesDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mFilesDir);\n        }\n    }\n\n    @Override\n    public FileInputStream openFileInput(String name)\n            throws FileNotFoundException {\n        if (getSubDirName() == null) {\n            return super.openFileInput(name);\n        }\n        File f = makeFilename(getFilesDir(), name);\n        return new FileInputStream(f);\n    }\n\n    @Override\n    public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException {\n        if (mode != MODE_PRIVATE || getSubDirName() == null) {\n            return super.openFileOutput(name, mode);\n        }\n        final boolean append = (mode & MODE_APPEND) != 0;\n        File f = makeFilename(getFilesDir(), name);\n        return new FileOutputStream(f, append);\n    }\n\n    @Override\n    public boolean deleteFile(String name) {\n        File f = makeFilename(getFilesDir(), name);\n        return f.delete();\n    }\n\n    @Override\n    public File getNoBackupFilesDir() {\n        if (getSubDirName() == null) {\n            return super.getNoBackupFilesDir();\n        }\n        synchronized (mSync) {\n            if (mNoBackupFilesDir == null) {\n                mNoBackupFilesDir = new File(super.getNoBackupFilesDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mNoBackupFilesDir);\n        }\n    }\n\n    @Override\n    public File getExternalFilesDir(String type) {\n        if (getSubDirName() == null) {\n            return super.getExternalFilesDir(type);\n        }\n        return ensurePrivateDirExists(new File(super.getExternalFilesDir(type), getSubDirName()));\n    }\n\n    @Override\n    public File[] getExternalFilesDirs(String type) {\n        if (getSubDirName() == null) {\n            return super.getExternalFilesDirs(type);\n        }\n        File[] superResult = super.getExternalFilesDirs(type);\n        File[] result = new File[superResult.length];\n        for (int i = 0; i < superResult.length; i++) {\n            result[i] = ensurePrivateDirExists(new File(superResult[i], getSubDirName()));\n        }\n        return result;\n    }\n\n    @Override\n    public File getObbDir() {\n        if (getSubDirName() == null) {\n            return super.getObbDir();\n        }\n        synchronized (mSync) {\n            if (mObbDir == null) {\n                mObbDir = new File(super.getObbDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mObbDir);\n        }\n    }\n\n    @Override\n    public File[] getObbDirs() {\n        if (getSubDirName() == null) {\n            return super.getObbDirs();\n        }\n        File[] superResult = super.getObbDirs();\n        File[] result = new File[superResult.length];\n        for (int i = 0; i < superResult.length; i++) {\n            result[i] = ensurePrivateDirExists(new File(superResult[i], getSubDirName()));\n        }\n        return result;\n    }\n\n    @Override\n    public File getCacheDir() {\n        if (getSubDirName() == null) {\n            return super.getCacheDir();\n        }\n        synchronized (mSync) {\n            if (mCacheDir == null) {\n                mCacheDir = new File(super.getCacheDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mCacheDir);\n        }\n    }\n\n    @Override\n    public File getCodeCacheDir() {\n        if (getSubDirName() == null) {\n            return super.getCodeCacheDir();\n        }\n        synchronized (mSync) {\n            if (mCodeCacheDir == null) {\n                mCodeCacheDir = new File(super.getCodeCacheDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mCodeCacheDir);\n        }\n    }\n\n    @Override\n    public File getExternalCacheDir() {\n        if (getSubDirName() == null) {\n            return super.getExternalCacheDir();\n        }\n        synchronized (mSync) {\n            if (mExternalCacheDir == null) {\n                mExternalCacheDir = new File(super.getExternalCacheDir(), getSubDirName());\n            }\n            return ensurePrivateDirExists(mExternalCacheDir);\n        }\n    }\n\n    @Override\n    public File[] getExternalCacheDirs() {\n        if (getSubDirName() == null) {\n            return super.getExternalCacheDirs();\n        }\n        File[] superResult = super.getExternalCacheDirs();\n        File[] result = new File[superResult.length];\n        for (int i = 0; i < superResult.length; i++) {\n            result[i] = ensurePrivateDirExists(new File(superResult[i], getSubDirName()));\n        }\n        return result;\n    }\n\n    @Override\n    public File[] getExternalMediaDirs() {\n        if (getSubDirName() == null) {\n            return super.getExternalMediaDirs();\n        }\n        File[] superResult = super.getExternalMediaDirs();\n        File[] result = new File[superResult.length];\n        for (int i = 0; i < superResult.length; i++) {\n            result[i] = ensurePrivateDirExists(new File(superResult[i], getSubDirName()));\n        }\n        return result;\n    }\n\n    @Override\n    public File getDir(String name, int mode) {\n        if (mode != MODE_PRIVATE || getSubDirName() == null) {\n            return super.getDir(name, mode);\n        }\n        return ensurePrivateDirExists(new File(super.getDir(name, mode), getSubDirName()));\n    }\n\n    @Override\n    public SharedPreferences getSharedPreferences(String name, int mode) {\n        if (mode != MODE_PRIVATE || getSubDirName() == null) {\n            return super.getSharedPreferences(name, mode);\n        } else {\n            return super.getSharedPreferences(makeSubName(name), mode);\n        }\n    }\n\n    @Override\n    public boolean deleteSharedPreferences(String name) {\n        if (getSubDirName() == null) {\n            return super.deleteSharedPreferences(name);\n        } else {\n            return super.deleteSharedPreferences(makeSubName(name));\n        }\n    }\n\n    @Override\n    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {\n        if (mode != MODE_PRIVATE || getSubDirName() == null) {\n            return super.openOrCreateDatabase(name, mode, factory);\n        } else {\n            return super.openOrCreateDatabase(makeSubName(name), mode, factory);\n        }\n    }\n\n    @Override\n    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {\n        if (mode != MODE_PRIVATE || getSubDirName() == null) {\n            return super.openOrCreateDatabase(name, mode, factory, errorHandler);\n        } else {\n            return super.openOrCreateDatabase(makeSubName(name), mode, factory, errorHandler);\n        }\n    }\n\n    @Override\n    public boolean moveDatabaseFrom(Context sourceContext, String name) {\n        if (getSubDirName() == null) {\n            return super.moveDatabaseFrom(sourceContext, name);\n        } else {\n            throw new UnsupportedOperationException(\"暂不支持\");\n        }\n    }\n\n    @Override\n    public boolean deleteDatabase(String name) {\n        if (getSubDirName() == null) {\n            return super.deleteDatabase(name);\n        } else {\n            return super.deleteDatabase(makeSubName(name));\n        }\n    }\n\n    @Override\n    public File getDatabasePath(String name) {\n        if (getSubDirName() == null\n                || name.charAt(0) == File.separatorChar) {\n            return super.getDatabasePath(name);\n        } else {\n            return super.getDatabasePath(makeSubName(name));\n        }\n    }\n\n    @Override\n    public String[] databaseList() {\n        if (getSubDirName() == null) {\n            return super.databaseList();\n        } else {\n            String[] databaseList = super.databaseList();\n            boolean[] record = new boolean[databaseList.length];\n            int size = 0;\n            for (int i = 0; i < databaseList.length; i++) {\n                if (databaseList[i].startsWith(getSubDirName())) {\n                    record[i] = true;\n                    size++;\n                } else {\n                    record[i] = false;\n                }\n            }\n            String[] result = new String[size];\n            int j = 0;\n            for (int i = 0; i < record.length; i++) {\n                if (record[i]) {\n                    result[j++] = databaseList[i];\n                }\n            }\n            return result;\n        }\n    }\n\n    private String makeSubName(String name) {\n        return getSubDirName() + \"_\" + name;\n    }\n\n    private static File ensurePrivateDirExists(File dir) {\n        //noinspection ResultOfMethodCallIgnored\n        dir.mkdirs();\n        return dir;\n    }\n\n    private static File makeFilename(File base, String name) {\n        if (name.indexOf(File.separatorChar) < 0) {\n            return new File(base, name);\n        }\n        throw new IllegalArgumentException(\n                \"File \" + name + \" contains a path separator\");\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/UriConverter.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.annotation.TargetApi;\nimport android.content.ContentResolver;\nimport android.database.ContentObserver;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\n\npublic class UriConverter {\n\n    private static UriParseDelegate sUriParseDelegate;\n\n    public static void setUriParseDelegate(UriParseDelegate sUriParseDelegate) {\n        UriConverter.sUriParseDelegate = sUriParseDelegate;\n    }\n\n    public interface UriParseDelegate {\n\n        Uri parse(String uriString);\n\n        Uri parseCall(String uriString, Bundle extra);\n    }\n\n    public static Uri parse(String uriString) {\n        if (sUriParseDelegate != null) {\n            return sUriParseDelegate.parse(uriString);\n        } else {\n            return Uri.parse(uriString);\n        }\n    }\n\n    public static Uri parseCall(String uriString, Bundle bundle) {\n        if (sUriParseDelegate != null) {\n            return sUriParseDelegate.parseCall(uriString, bundle);\n        } else {\n            return Uri.parse(uriString);\n        }\n    }\n\n    public static Uri build(Uri.Builder builder) {\n        String uri = builder.build().toString();\n        return parse(uri);\n    }\n\n    public static Bundle call(ContentResolver resolver, Uri uri, String method, String arg, Bundle extras) {\n        if (extras == null) {\n            extras = new Bundle();\n        }\n        Uri containerUri = UriConverter.parseCall(uri.toString(), extras);\n        return resolver.call(containerUri, method, arg, extras);\n    }\n\n    public static void notifyChange(ContentResolver resolver, Uri uri, ContentObserver observer) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        resolver.notifyChange(containerUri, observer);\n    }\n\n    public static void notifyChange(ContentResolver resolver, Uri uri, ContentObserver observer,\n                                    boolean syncToNetwork) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        resolver.notifyChange(containerUri, observer, syncToNetwork);\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    public static void notifyChange(ContentResolver resolver, Uri uri, ContentObserver observer,\n                                    int flags) {\n        Uri containerUri = UriConverter.parse(uri.toString());\n        resolver.notifyChange(containerUri, observer, flags);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/XmlPullParserUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.res.Resources;\nimport android.content.res.XmlResourceParser;\nimport android.view.InflateException;\n\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\npublic class XmlPullParserUtil {\n\n    public static String getLayoutStartTagName(Resources res, int layoutResID) {\n        XmlResourceParser parser;\n        String name;\n        try {\n            int type;\n            parser = res.getLayout(layoutResID);\n            while ((type = parser.next()) != XmlPullParser.START_TAG &&\n                    type != XmlPullParser.END_DOCUMENT) {\n                // Empty\n            }\n\n            if (type != XmlPullParser.START_TAG) {\n                throw new InflateException(parser.getPositionDescription()\n                        + \": No start tag found!\");\n            }\n            name = parser.getName();\n        } catch (XmlPullParserException e) {\n            final InflateException ie = new InflateException(e.getMessage(), e);\n            ie.setStackTrace(new StackTraceElement[0]);\n            throw ie;\n        } catch (Exception e) {\n            final InflateException ie = new InflateException(e.getMessage(), e);\n            ie.setStackTrace(new StackTraceElement[0]);\n            throw ie;\n        }\n        return name;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/package-info.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n// TODO #34 将runtime中的复杂逻辑移到loader中去\npackage com.tencent.shadow.core.runtime;"
  },
  {
    "path": "projects/sdk/core/settings.gradle",
    "content": "includeBuild '../coding'\ninclude 'loader',\n        'runtime',\n        'activity-container',\n        'transform',\n        'gradle-plugin',\n        'common',\n        'manager',\n        'manager-db-test',\n        'manifest-parser',\n        'load-parameters',\n        'transform-kit',\n        'utils'"
  },
  {
    "path": "projects/sdk/core/transform/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/transform/build.gradle",
    "content": "apply plugin: 'kotlin'\n\ndependencies {\n    api project(':transform-kit')\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation project(path: ':transform-kit', configuration: 'tests')\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n        apiVersion = \"1.3\"// 兼容低版本Gradle和https://youtrack.jetbrains.com/issue/KT-39389\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/DeprecatedTransformWrapper.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.tencent.shadow.core.transform\n\nimport com.android.build.api.transform.DirectoryInput\nimport com.android.build.api.transform.Format\nimport com.android.build.api.transform.JarInput\nimport com.android.build.api.transform.QualifiedContent\nimport com.android.build.api.transform.SecondaryFile\nimport com.android.build.api.transform.Transform\nimport com.android.build.api.transform.TransformInvocation\nimport com.android.build.api.transform.TransformOutputProvider\nimport com.android.build.api.variant.VariantInfo\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport com.google.common.collect.ImmutableList\nimport com.google.common.io.Files\nimport com.tencent.shadow.core.transform_kit.ClassTransform\nimport com.tencent.shadow.core.transform_kit.TransformInput\nimport org.gradle.api.Project\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipOutputStream\n\n/**\n * 适配ClassTransform到AGP 8之前的Transform API\n * 这是最初开发的、长期使用的比较稳定的实现。因为AGP 8去掉了这个API，所以不得不做两种适配。\n */\nclass DeprecatedTransformWrapper(\n    val project: Project, val classTransform: ClassTransform\n) : Transform() {\n    override fun getName(): String = \"ShadowTransform\"\n\n    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> =\n        TransformManager.CONTENT_CLASS\n\n    override fun isIncremental(): Boolean = false\n\n    override fun isCacheable(): Boolean {\n        return true\n    }\n\n    override fun applyToVariant(variant: VariantInfo): Boolean {\n        return if (variant.isTest) false\n        else variant.flavorNames.contains(ShadowTransform.ApplyShadowTransformFlavorName)\n    }\n\n    override fun getScopes(): MutableSet<in QualifiedContent.Scope> =\n        TransformManager.SCOPE_FULL_PROJECT\n\n    override fun transform(invocation: TransformInvocation) {\n        //before Transform clean output\n        val outputProvider = invocation.outputProvider\n        outputProvider.deleteAll()\n\n        val inputs: List<TransformInput> = invocation.inputs.flatMap { transformInput ->\n            transformInput.directoryInputs.map {\n                TransformInputImpl(it)\n            } + transformInput.jarInputs.map {\n                TransformInputImpl(it)\n            }\n        }\n\n        classTransform.beforeTransform()\n        classTransform.input(inputs)\n        classTransform.onTransform()\n        output(inputs, outputProvider)\n        classTransform.afterTransform()\n    }\n\n    /**\n     * 这个旧版Transform API需要把DIR里的class和jar里的class分别输出到DIR和对应的jar里，\n     * 这个行为是API决定的，不是通用的，因此要写在这里。对于ClassTransform来说，唯一可以复用的\n     * 就是把class输出到OutputStream中。\n     */\n    private fun output(inputs: Iterable<TransformInput>, outputProvider: TransformOutputProvider) {\n        inputs.forEach { transformInput ->\n            transformInput as TransformInputImpl\n\n            val outputLocation = outputProvider.getContentLocation(\n                transformInput.name,\n                transformInput.contentTypes,\n                transformInput.scopes,\n                transformInput.format\n            )\n\n            when (transformInput.kind) {\n                TransformInput.Kind.DIRECTORY -> {\n                    transformInput.inputClassNames.forEach { className ->\n                        val relativePath = className.replace('.', File.separatorChar) + \".class\"\n                        val outputClassFile = File(outputLocation, relativePath)\n                        Files.createParentDirs(outputClassFile)\n                        FileOutputStream(outputClassFile).use {\n                            classTransform.onOutputClass(className, it)\n                        }\n                    }\n                }\n\n                TransformInput.Kind.JAR -> {\n                    Files.createParentDirs(outputLocation)\n                    ZipOutputStream(FileOutputStream(outputLocation)).use { zos ->\n                        transformInput.inputClassNames.forEach { className ->\n                            val entryName = className.replace('.', '/') + \".class\"\n                            zos.putNextEntry(ZipEntry(entryName))\n                            classTransform.onOutputClass(className, zos)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n\n    override fun getSecondaryFiles(): ImmutableList<SecondaryFile>? {\n        val transformJar = File(this::class.java.protectionDomain.codeSource.location.toURI())\n        val transformKitJar =\n            File(ClassTransform::class.java.protectionDomain.codeSource.location.toURI())\n\n        return ImmutableList.of(\n            //将当前类运行所在的jar本身作为转换输入的SecondaryFiles，也就作为了这个transform task的inputs的\n            //一部分，这使得当这个Transform程序变化时，构建能检测到这个Transform需要重新执行。这是直接编辑这个\n            //Transform源码后，应用了这个Plugin的debug工程能直接生效的关键。\n            SecondaryFile.nonIncremental(project.files(transformJar)),\n            SecondaryFile.nonIncremental(project.files(transformKitJar))\n        )\n    }\n\n    private class TransformInputImpl(\n        val file: File,\n        val name: String,\n        val contentTypes: Set<QualifiedContent.ContentType>,\n        val scopes: MutableSet<in QualifiedContent.Scope>,\n        val format: Format,\n        override val kind: Kind,\n    ) : TransformInput() {\n        constructor(di: DirectoryInput) : this(\n            di.file,\n            di.name,\n            di.contentTypes,\n            di.scopes,\n            Format.DIRECTORY,\n            Kind.DIRECTORY\n        )\n\n        constructor(ji: JarInput) : this(\n            ji.file,\n            ji.name,\n            ji.contentTypes,\n            ji.scopes,\n            Format.JAR,\n            Kind.JAR\n        )\n\n        override fun asFile(): File = file\n\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/GradleTransformWrapper.kt",
    "content": "package com.tencent.shadow.core.transform\n\nimport com.tencent.shadow.core.transform_kit.ClassTransform\nimport com.tencent.shadow.core.transform_kit.TransformInput\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.file.Directory\nimport org.gradle.api.file.RegularFile\nimport org.gradle.api.file.RegularFileProperty\nimport org.gradle.api.provider.ListProperty\nimport org.gradle.api.tasks.InputFiles\nimport org.gradle.api.tasks.Internal\nimport org.gradle.api.tasks.OutputFile\nimport org.gradle.api.tasks.TaskAction\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.util.jar.JarOutputStream\nimport java.util.zip.ZipEntry\nimport javax.inject.Inject\n\n/**\n * 适配AGP 7.2引入的新的Transform API，AGP没给这个API特别命名，但我们知道它是Gradle直接提供的Transform\n * 接口。与之对应的是DeprecatedTransformWrapper适配旧的Transform API接口。\n */\nabstract class GradleTransformWrapper @Inject constructor(@Internal val classTransform: ClassTransform) :\n    DefaultTask() {\n    // This property will be set to all Jar files available in scope\n    @get:InputFiles\n    abstract val allJars: ListProperty<RegularFile>\n\n    // Gradle will set this property with all class directories that available in scope\n    @get:InputFiles\n    abstract val allDirectories: ListProperty<Directory>\n\n    // Task will put all classes from directories and jars after optional modification into single jar\n    @get:OutputFile\n    abstract val output: RegularFileProperty\n\n    @TaskAction\n    fun taskAction() {\n        val inputs: List<TransformInput> = allDirectories.get().map {\n            TransformInputImpl(it.asFile, TransformInput.Kind.DIRECTORY)\n        } + allJars.get().map {\n            TransformInputImpl(it.asFile, TransformInput.Kind.JAR)\n        }\n\n        classTransform.beforeTransform()\n        classTransform.input(inputs)\n        classTransform.onTransform()\n        output(inputs)\n        classTransform.afterTransform()\n    }\n\n    private fun output(inputs: Iterable<TransformInput>) {\n        val jarOutput = JarOutputStream(\n            BufferedOutputStream(FileOutputStream(output.get().asFile))\n        )\n        jarOutput.use {\n            val outputClassNames = inputs.flatMap {\n                it.inputClassNames\n            }\n\n            outputClassNames.forEach { className ->\n                val entryName = className.replace('.', '/') + \".class\"\n                jarOutput.putNextEntry(ZipEntry(entryName))\n                classTransform.onOutputClass(className, jarOutput)\n            }\n        }\n    }\n\n    private class TransformInputImpl(\n        val file: File,\n        override val kind: Kind\n    ) : TransformInput() {\n\n        override fun asFile(): File = file\n\n    }\n}\n\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/ShadowTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransform\nimport com.tencent.shadow.core.transform_kit.AbstractTransformManager\nimport com.tencent.shadow.core.transform_kit.ClassPoolBuilder\nimport org.gradle.api.Project\n\nclass ShadowTransform(\n    project: Project,\n    classPoolBuilder: ClassPoolBuilder,\n    private val useHostContext: () -> Array<String>\n) : AbstractTransform(project, classPoolBuilder) {\n    companion object {\n        const val SelfClassNamePlaceholder =\n            \"com.tencent.shadow.core.transform.SelfClassNamePlaceholder\"\n        const val DimensionName = \"Shadow\"\n        const val NoShadowTransformFlavorName = \"normal\"\n        const val ApplyShadowTransformFlavorName = \"plugin\"\n    }\n\n    lateinit var _mTransformManager: TransformManager\n\n    override val mTransformManager: AbstractTransformManager\n        get() = _mTransformManager\n\n    override fun beforeTransform() {\n        super.beforeTransform()\n        _mTransformManager = TransformManager(classPool, useHostContext)\n        classPool.makeInterface(SelfClassNamePlaceholder)\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/TransformManager.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform\n\nimport com.tencent.shadow.core.transform.specific.ActivityOptionsSupportTransform\nimport com.tencent.shadow.core.transform.specific.ActivityTransform\nimport com.tencent.shadow.core.transform.specific.AppComponentFactoryTransform\nimport com.tencent.shadow.core.transform.specific.ApplicationTransform\nimport com.tencent.shadow.core.transform.specific.ContentProviderTransform\nimport com.tencent.shadow.core.transform.specific.DialogSupportTransform\nimport com.tencent.shadow.core.transform.specific.FragmentSupportTransform\nimport com.tencent.shadow.core.transform.specific.InstrumentationTransform\nimport com.tencent.shadow.core.transform.specific.IntentServiceTransform\nimport com.tencent.shadow.core.transform.specific.KeepHostContextTransform\nimport com.tencent.shadow.core.transform.specific.LayoutInflaterTransform\nimport com.tencent.shadow.core.transform.specific.PackageItemInfoTransform\nimport com.tencent.shadow.core.transform.specific.PackageManagerTransform\nimport com.tencent.shadow.core.transform.specific.ReceiverSupportTransform\nimport com.tencent.shadow.core.transform.specific.ServiceTransform\nimport com.tencent.shadow.core.transform.specific.WebViewTransform\nimport com.tencent.shadow.core.transform_kit.AbstractTransformManager\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport javassist.ClassPool\n\nclass TransformManager(\n    classPool: ClassPool,\n    useHostContext: () -> Array<String>\n) : AbstractTransformManager(classPool) {\n\n    /**\n     * 按这个列表的顺序应用各子Transform逻辑。\n     *\n     * 注意这个列表的顺序是有关系的，\n     * 比如在ActivityTransform之前的Transform可以看到原本的Activity类型，\n     * 在其之后的Transform在插件中就看不到Activity类型了，\n     * 所有有些Transform在获取方法时要将原本的Activity类型改为ShadowActivity类型，\n     * 因为ActivityTransform在它之前已经生效了。\n     */\n    override val mTransformList: List<SpecificTransform> = listOf(\n        ApplicationTransform(),\n        ActivityTransform(),\n        ServiceTransform(),\n        IntentServiceTransform(),\n        InstrumentationTransform(),\n        FragmentSupportTransform(),\n        DialogSupportTransform(),\n        WebViewTransform(),\n        ContentProviderTransform(),\n        PackageManagerTransform(),\n        PackageItemInfoTransform(),\n        AppComponentFactoryTransform(),\n        LayoutInflaterTransform(),\n        KeepHostContextTransform(useHostContext()),\n        ActivityOptionsSupportTransform(),\n        ReceiverSupportTransform(),\n    )\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ActivityOptionsSupportTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.bytecode.Descriptor\n\n/**\n * ActivityOptions有些方法带有Activity类型参数，\n * 由于它是系统类，不会被Transform修改，所以我们需要将插件中调用这些方法的代码修改一下。\n * 转调一下，转换ShadowActivity到PluginContainerActivity，再调用原方法。\n */\nclass ActivityOptionsSupportTransform : SpecificTransform() {\n    companion object {\n        const val ActivityOptionsClassname = \"android.app.ActivityOptions\"\n        const val ActivityOptionsSupportClassname =\n            \"com.tencent.shadow.core.runtime.ActivityOptionsSupport\"\n        const val ActivityClassname = \"android.app.Activity\"\n        const val ShadowActivityClassname = \"com.tencent.shadow.core.runtime.ShadowActivity\"\n        const val makeSceneTransitionAnimationMethodName = \"makeSceneTransitionAnimation\"\n        fun makeSceneTransitionAnimationMethodSig1(activityClassname: String) =\n            \"(L${Descriptor.toJvmName(activityClassname)};\" +\n                    \"Landroid/view/View;Ljava/lang/String;)Landroid/app/ActivityOptions;\"\n\n        fun makeSceneTransitionAnimationMethodSig2(activityClassname: String) =\n            \"(L${Descriptor.toJvmName(activityClassname)};\" +\n                    \"[Landroid/util/Pair;)Landroid/app/ActivityOptions;\"\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        newStep(object : TransformStep {\n            val codeConverter = CodeConverter()\n\n            init {\n                val activityOptionsClass = mClassPool[ActivityOptionsClassname]\n                val activityOptionsSupportClass = mClassPool[ActivityOptionsSupportClassname]\n\n                listOf(\n                    ::makeSceneTransitionAnimationMethodSig1,\n                    ::makeSceneTransitionAnimationMethodSig2,\n                ).forEach { sig ->\n                    val originalMethod = activityOptionsClass.getMethod(\n                        makeSceneTransitionAnimationMethodName,\n                        sig(ActivityClassname)\n                    )\n                    //appClass中的Activity都已经被改名为ShadowActivity了．所以要把方法签名也先改一下．\n                    originalMethod.methodInfo.descriptor =\n                        sig(ShadowActivityClassname)\n\n                    val supportMethod = activityOptionsSupportClass.getMethod(\n                        makeSceneTransitionAnimationMethodName,\n                        sig(ShadowActivityClassname)\n                    )\n                    codeConverter.redirectMethodCall(originalMethod, supportMethod)\n                }\n            }\n\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(ActivityOptionsClassname))\n\n            override fun transform(ctClass: CtClass) {\n                ctClass.defrost()\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                    throw e\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ActivityTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nclass ActivityTransform : SimpleRenameTransform(\n    mapOf(\n        \"android.app.Activity\"\n                to \"com.tencent.shadow.core.runtime.ShadowActivity\",\n        \"android.app.NativeActivity\"\n                to \"com.tencent.shadow.core.runtime.ShadowNativeActivity\"\n    )\n)\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/AppComponentFactoryTransform.kt",
    "content": "package com.tencent.shadow.core.transform.specific\n\nclass AppComponentFactoryTransform : SimpleRenameTransform(\n    mapOf(\n        \"android.app.AppComponentFactory\"\n                to \"com.tencent.shadow.core.runtime.ShadowAppComponentFactory\"\n    )\n)\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ApplicationTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nclass ApplicationTransform : SimpleRenameTransform(\n    mapOf(\n        \"android.app.Application\"\n                to \"com.tencent.shadow.core.runtime.ShadowApplication\",\n        \"android.app.Application\\$ActivityLifecycleCallbacks\"\n                to \"com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks\"\n    )\n)\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ContentProviderTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.ClassPool\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.bytecode.Descriptor\n\nclass ContentProviderTransform : SpecificTransform() {\n    companion object {\n        const val ShadowUriClassname = \"com.tencent.shadow.core.runtime.UriConverter\"\n        const val AndroidUriClassname = \"android.net.Uri\"\n        const val uriBuilderName = \"android.net.Uri\\$Builder\"\n        const val resolverName = \"android.content.ContentResolver\"\n    }\n\n    private fun prepareUriParseCodeConverter(classPool: ClassPool): CodeConverter {\n        val uriMethod = mClassPool[AndroidUriClassname].methods!!\n        val shadowUriMethod = mClassPool[ShadowUriClassname].methods!!\n\n        val method_parse = uriMethod.filter { it.name == \"parse\" }\n        val shadow_method_parse = shadowUriMethod.filter { it.name == \"parse\" }!!\n        val codeConverter = CodeConverter()\n\n        for (ctAndroidMethod in method_parse) {\n            for (ctShadowMedthod in shadow_method_parse) {\n                if (ctAndroidMethod.methodInfo.descriptor == ctShadowMedthod.methodInfo.descriptor) {\n                    codeConverter.redirectMethodCall(ctAndroidMethod, ctShadowMedthod)\n                }\n            }\n        }\n        return codeConverter\n    }\n\n    private fun prepareUriBuilderCodeConverter(classPool: ClassPool): CodeConverter {\n        val uriClass = mClassPool[AndroidUriClassname]\n        val uriBuilderClass = mClassPool[uriBuilderName]\n        val buildMethod = uriBuilderClass.getMethod(\"build\", Descriptor.ofMethod(uriClass, null))\n        val newBuildMethod = mClassPool[ShadowUriClassname].getMethod(\n            \"build\",\n            Descriptor.ofMethod(uriClass, arrayOf(uriBuilderClass))\n        )\n        val codeConverter = CodeConverter()\n        codeConverter.redirectMethodCallToStatic(buildMethod, newBuildMethod)\n        return codeConverter\n    }\n\n    private fun prepareContentResolverCodeConverter(classPool: ClassPool): CodeConverter {\n        val codeConverter = CodeConverter()\n        val resolverClass = classPool[resolverName]\n        val targetClass = classPool[ShadowUriClassname]\n        val uriClass = classPool[\"android.net.Uri\"]\n        val stringClass = classPool[\"java.lang.String\"]\n        val bundleClass = classPool[\"android.os.Bundle\"]\n        val observerClass = classPool[\"android.database.ContentObserver\"]\n\n        val callMethod = resolverClass.getMethod(\n            \"call\", Descriptor.ofMethod(\n                bundleClass,\n                arrayOf(uriClass, stringClass, stringClass, bundleClass)\n            )\n        )\n        val newCallMethod = targetClass.getMethod(\n            \"call\", Descriptor.ofMethod(\n                bundleClass,\n                arrayOf(resolverClass, uriClass, stringClass, stringClass, bundleClass)\n            )\n        )\n        codeConverter.redirectMethodCallToStatic(callMethod, newCallMethod)\n\n        val notifyMethod1 = resolverClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(uriClass, observerClass)\n            )\n        )\n        val newNotifyMethod1 = targetClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(resolverClass, uriClass, observerClass)\n            )\n        )\n        codeConverter.redirectMethodCallToStatic(notifyMethod1, newNotifyMethod1)\n\n        val notifyMethod2 = resolverClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(uriClass, observerClass, CtClass.booleanType)\n            )\n        )\n        val newNotifyMethod2 = targetClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(resolverClass, uriClass, observerClass, CtClass.booleanType)\n            )\n        )\n        codeConverter.redirectMethodCallToStatic(notifyMethod2, newNotifyMethod2)\n\n        val notifyMethod3 = resolverClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(uriClass, observerClass, CtClass.intType)\n            )\n        )\n        val newNotifyMethod3 = targetClass.getMethod(\n            \"notifyChange\", Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(resolverClass, uriClass, observerClass, CtClass.intType)\n            )\n        )\n        codeConverter.redirectMethodCallToStatic(notifyMethod3, newNotifyMethod3)\n\n        return codeConverter\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n\n        val uriParseCodeConverter = prepareUriParseCodeConverter(mClassPool)\n        val uriBuilderCodeConverter = prepareUriBuilderCodeConverter(mClassPool)\n        val contentResolverCodeConverter = prepareContentResolverCodeConverter(mClassPool)\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(AndroidUriClassname))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(uriParseCodeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(uriBuilderName))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(uriBuilderCodeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(resolverName))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(contentResolverCodeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/DialogSupportTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.bytecode.Descriptor\n\nclass DialogSupportTransform : SpecificTransform() {\n    companion object {\n        const val ShadowActivityClassname = \"com.tencent.shadow.core.runtime.ShadowActivity\"\n        const val AndroidDialogClassname = \"android.app.Dialog\"\n        const val DialogSupportTransformClassname =\n            \"com.tencent.shadow.core.runtime.ShadowDialogSupport\"\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        val androidDialog = mClassPool[AndroidDialogClassname]\n        val shadowDialogSupport = mClassPool[DialogSupportTransformClassname]\n        val shadowActivity = mClassPool[ShadowActivityClassname]\n\n        val setOwnerActivityMethod = androidDialog.getDeclaredMethod(\"setOwnerActivity\")\n        val getOwnerActivityMethod = androidDialog.getDeclaredMethod(\"getOwnerActivity\")\n\n        //appClass中的Activity都已经被改名为ShadowActivity了．所以要把方法签名也先改一下．\n        val shadowActivitySig = \"Lcom/tencent/shadow/core/runtime/ShadowActivity;\"\n        setOwnerActivityMethod.methodInfo.descriptor = \"($shadowActivitySig)V\"\n        getOwnerActivityMethod.methodInfo.descriptor = \"()$shadowActivitySig\"\n\n        val dialogSetOwnerActivityMethod = shadowDialogSupport.getMethod(\n            \"dialogSetOwnerActivity\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidDialog, shadowActivity)\n            )\n        )\n        val dialogGetOwnerActivityMethod = shadowDialogSupport.getMethod(\n            \"dialogGetOwnerActivity\",\n            Descriptor.ofMethod(\n                shadowActivity,\n                arrayOf(androidDialog)\n            )\n        )\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = allInputClass\n\n            override fun transform(ctClass: CtClass) {\n                ctClass.defrost()\n                val codeConverter = CodeConverter()\n                codeConverter.redirectMethodCallToStatic(\n                    setOwnerActivityMethod,\n                    dialogSetOwnerActivityMethod\n                )\n                codeConverter.redirectMethodCallToStatic(\n                    getOwnerActivityMethod,\n                    dialogGetOwnerActivityMethod\n                )\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                    throw e\n                }\n            }\n        })\n\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.*\nimport javassist.bytecode.Descriptor\n\nclass FragmentSupportTransform : SpecificTransform() {\n    companion object {\n        const val ObjectClassname = \"java.lang.Object\"\n        const val ShadowActivityClassname = \"com.tencent.shadow.core.runtime.ShadowActivity\"\n        const val AndroidActivityClassname = \"android.app.Activity\"\n        const val AndroidFragmentClassname = \"android.app.Fragment\"\n        const val AndroidIntentClassname = \"android.content.Intent\"\n        const val AndroidBundleClassname = \"android.os.Bundle\"\n        const val AndroidContextClassname = \"android.content.Context\"\n        const val AndroidAttributeSetClassname = \"android.util.AttributeSet\"\n        const val ShadowFragmentSupportClassname =\n            \"com.tencent.shadow.core.runtime.ShadowFragmentSupport\"\n    }\n\n    private fun CtClass.isFragment(): Boolean = isClassOf(AndroidFragmentClassname)\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        val javaObject = mClassPool[ObjectClassname]\n        val androidActivity = mClassPool[AndroidActivityClassname]\n        val androidFragment = mClassPool[AndroidFragmentClassname]\n        val androidIntent = mClassPool[AndroidIntentClassname]\n        val androidBundle = mClassPool[AndroidBundleClassname]\n        val androidContext = mClassPool[AndroidContextClassname]\n        val androidAttributeSet = mClassPool[AndroidAttributeSetClassname]\n        val shadowActivity = mClassPool[ShadowActivityClassname]\n        val shadowFragmentSupport = mClassPool[ShadowFragmentSupportClassname]\n        val getActivityMethod = androidFragment.getDeclaredMethod(\"getActivity\")\n        val getContextMethod = androidFragment.getDeclaredMethod(\"getContext\")\n        val getHostMethod = androidFragment.getDeclaredMethod(\"getHost\")\n        val fragmentGetActivityMethod = shadowFragmentSupport.getMethod(\n            \"fragmentGetActivity\",\n            Descriptor.ofMethod(\n                shadowActivity,\n                arrayOf(androidFragment)\n            )\n        )\n        val fragmentGetContextMethod = shadowFragmentSupport.getMethod(\n            \"fragmentGetContext\",\n            Descriptor.ofMethod(\n                androidContext,\n                arrayOf(androidFragment)\n            )\n        )\n        val fragmentGetHostMethod = shadowFragmentSupport.getMethod(\n            \"fragmentGetHost\",\n            Descriptor.ofMethod(\n                javaObject,\n                arrayOf(androidFragment)\n            )\n        )\n\n        mClassPool.importPackage(\"android.app\")\n        mClassPool.importPackage(\"android.content\")\n        mClassPool.importPackage(\"android.util\")\n        mClassPool.importPackage(\"android.os\")\n        mClassPool.importPackage(\"com.tencent.shadow.core.runtime\")\n\n        //appClass中的Activity都已经被改名为ShadowActivity了．所以要把方法签名也先改一下．\n        getActivityMethod.methodInfo.descriptor =\n            \"()Lcom/tencent/shadow/core/runtime/ShadowActivity;\"\n\n        val startActivityMethod1 = androidFragment.getMethod(\n            \"startActivity\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidIntent)\n            )\n        )\n        val fragmentStartActivityMethod1 = shadowFragmentSupport.getMethod(\n            \"fragmentStartActivity\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidFragment, androidIntent)\n            )\n        )\n        val startActivityMethod2 = androidFragment.getMethod(\n            \"startActivity\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidIntent, androidBundle)\n            )\n        )\n        val fragmentStartActivityMethod2 = shadowFragmentSupport.getMethod(\n            \"fragmentStartActivity\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidFragment, androidIntent, androidBundle)\n            )\n        )\n\n        val startActivityForResultMethod1 = androidFragment.getMethod(\n            \"startActivityForResult\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidIntent, CtClass.intType)\n            )\n        )\n        val fragmentStartActivityForResultMethod1 = shadowFragmentSupport.getMethod(\n            \"fragmentStartActivityForResult\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidFragment, androidIntent, CtClass.intType)\n            )\n        )\n        val startActivityForResultMethod2 = androidFragment.getMethod(\n            \"startActivityForResult\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidIntent, CtClass.intType, androidBundle)\n            )\n        )\n        val fragmentStartActivityForResultMethod2 = shadowFragmentSupport.getMethod(\n            \"fragmentStartActivityForResult\",\n            Descriptor.ofMethod(\n                CtClass.voidType,\n                arrayOf(androidFragment, androidIntent, CtClass.intType, androidBundle)\n            )\n        )\n\n        /**\n         * 调用插件Fragment的Activity相关方法时将ContainerActivity转换成ShadowActivity\n         */\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = allInputClass\n\n            override fun transform(ctClass: CtClass) {\n                ctClass.defrost()\n                val codeConverter = EnhancedCodeConverter()\n                codeConverter.redirectMethodCallToStatic(\n                    getActivityMethod,\n                    fragmentGetActivityMethod\n                )\n                codeConverter.redirectMethodCallExceptSuperCallToStatic(\n                    getContextMethod,\n                    fragmentGetContextMethod\n                )\n                codeConverter.redirectMethodCallToStatic(getHostMethod, fragmentGetHostMethod)\n                codeConverter.redirectMethodCallToStatic(\n                    startActivityMethod1,\n                    fragmentStartActivityMethod1\n                )\n                codeConverter.redirectMethodCallToStatic(\n                    startActivityMethod2,\n                    fragmentStartActivityMethod2\n                )\n                codeConverter.redirectMethodCallToStatic(\n                    startActivityForResultMethod1,\n                    fragmentStartActivityForResultMethod1\n                )\n                codeConverter.redirectMethodCallToStatic(\n                    startActivityForResultMethod2,\n                    fragmentStartActivityForResultMethod2\n                )\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                    throw e\n                }\n            }\n        })\n\n        fun onAttachSupport() {\n            //收集哪些Fragment覆盖了onAttach方法\n            val overrideOnAttachContextFragments = mutableSetOf<CtClass>()\n            val overrideOnAttachActivityFragments = mutableSetOf<CtClass>()\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = allInputClass\n                    .filter { it.isFragment() }\n                    .toSet()\n\n                override fun transform(ctClass: CtClass) {\n                    val onAttachContext: CtMethod? =\n                        try {\n                            ctClass.getDeclaredMethod(\"onAttach\", arrayOf(androidContext))\n                        } catch (e: NotFoundException) {\n                            null\n                        }\n                    val onAttachActivity: CtMethod? =\n                        try {\n                            ctClass.getDeclaredMethod(\"onAttach\", arrayOf(shadowActivity))\n                        } catch (e: NotFoundException) {\n                            null\n                        }\n                    if (onAttachContext != null) {\n                        overrideOnAttachContextFragments.add(ctClass)\n                    }\n                    if (onAttachActivity != null) {\n                        overrideOnAttachActivityFragments.add(ctClass)\n                    }\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnAttachContextFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    val onAttachContext: CtMethod =\n                        ctClass.getDeclaredMethod(\"onAttach\", arrayOf(androidContext))\n                    //原来的onAttach方法改名为onAttachShadowContext\n                    onAttachContext.name = \"onAttachShadowContext\"\n                    onAttachContext.modifiers = Modifier.setPrivate(onAttachContext.modifiers)\n\n                    //重新定义onAttach方法\n                    val newOnAttachContext = CtMethod.make(\n                        \"\"\"\n                        public void onAttach(Context context) {\n                            Context pluginActivity = ShadowFragmentSupport.toPluginContext(context);\n                            onAttachShadowContext(pluginActivity);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n                    ctClass.addMethod(newOnAttachContext)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnAttachActivityFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    val onAttachActivity: CtMethod =\n                        ctClass.getDeclaredMethod(\"onAttach\", arrayOf(shadowActivity))\n                    //原来的onAttach方法改名为onAttachShadowActivity\n                    onAttachActivity.name = \"onAttachShadowActivity\"\n                    onAttachActivity.modifiers = Modifier.setPrivate(onAttachActivity.modifiers)\n\n                    //重新定义onAttach方法\n                    val newOnAttachActivity = CtMethod.make(\n                        \"\"\"\n                        public void onAttach(Activity activity) {\n                            ShadowActivity shadowActivity = (ShadowActivity)ShadowFragmentSupport.toPluginContext(activity);\n                            onAttachShadowActivity(shadowActivity);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n                    ctClass.addMethod(newOnAttachActivity)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnAttachContextFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    //定义将插件Activity还原为ContainerActivity再调用super.onAttach方法的转调方法\n                    val superOnAttach = CtMethod.make(\n                        \"\"\"\n                        private void superOnAttach(Context context) {\n                            Context pluginContainerActivity = ShadowFragmentSupport.toOriginalContext(context);\n                            super.onAttach(pluginContainerActivity);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n\n                    //将插件Fragment中对super.onAttach的调用改调到superOnAttach上\n                    val codeConverter = CodeConverter()\n                    val superOnAttachContext: CtMethod =\n                        ctClass.superclass.getMethod(\"onAttach\", \"(Landroid/content/Context;)V\")\n                    codeConverter.redirectMethodCall(superOnAttachContext, superOnAttach)\n                    try {\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n\n                    /**\n                     * 一定要在super.onAttach转调到superOnAttach之后\n                     * 再把superOnAttach添加到ctClass上，避免superOnAttach中的\n                     * super.onAttach也被改成superOnAttach\n                     */\n                    ctClass.addMethod(superOnAttach)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnAttachActivityFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    //定义将插件Activity还原为ContainerActivity再调用super.onAttach方法的转调方法\n\n                    val superOnAttach =\n                        fixSuperOnAttachCall(ctClass) {\n                            CtMethod.make(\n                                \"\"\"\n                        private void superOnAttach(ShadowActivity shadowActivity) {\n                            Activity pluginContainerActivity = (Activity)ShadowFragmentSupport.toOriginalContext(shadowActivity);\n                            super.onAttach(pluginContainerActivity);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                            )\n                        }\n\n                    //将插件Fragment中对super.onAttach的调用改调到superOnAttach上\n                    val codeConverter = CodeConverter()\n                    var superOnAttachActivity: CtMethod =\n                        androidFragment.getDeclaredMethod(\"onAttach\", arrayOf(androidActivity))\n                    superOnAttachActivity =\n                        CtNewMethod.copy(superOnAttachActivity, androidFragment, null)\n                    superOnAttachActivity.methodInfo.descriptor =\n                        \"(Lcom/tencent/shadow/core/runtime/ShadowActivity;)V\"\n                    codeConverter.redirectMethodCall(superOnAttachActivity, superOnAttach)\n                    try {\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n\n                    /**\n                     * 一定要在super.onAttach转调到superOnAttach之后\n                     * 再把superOnAttach添加到ctClass上，避免superOnAttach中的\n                     * super.onAttach也被改成superOnAttach\n                     */\n                    ctClass.addMethod(superOnAttach)\n                }\n\n                /**\n                 * Javassist疑似有bug，当super类存在满足签名的方法时，就不会去父类的父类中查找更加准确匹配的方法了。\n                 * 导致当父类只Override了onAttach(Context)方法时，我们定义的superOnAttach方法中的\n                 * super.onAttach(Activity)调用会编译成对onAttach(Context)的调用。这与正常的Javac编译结果不一致。\n                 *\n                 * 因此，在这里如果父类没有定义onAttach(Activity)，先为它添加上，make后再移除。\n                 */\n                private fun fixSuperOnAttachCall(ctClass: CtClass, make: () -> CtMethod): CtMethod {\n                    val superclass = ctClass.superclass\n                    val needFix = try {\n                        superclass.getDeclaredMethod(\"onAttach\", arrayOf(androidActivity))\n                        false\n                    } catch (e: NotFoundException) {\n                        true\n                    }\n                    return if (needFix) {\n                        superclass.defrost()\n                        val newOnAttachActivity = CtMethod.make(\n                            \"\"\"\n                                public void onAttach(Activity activity) {}\n                            \"\"\".trimIndent(), superclass\n                        )\n                        superclass.addMethod(newOnAttachActivity)\n                        val result = make()\n                        superclass.removeMethod(newOnAttachActivity)\n                        result\n                    } else {\n                        make()\n                    }\n                }\n            })\n        }\n        onAttachSupport()\n\n        fun onInflateSupport() {\n            //收集哪些Fragment覆盖了onInflate方法\n            val overrideOnInflateContextFragments = mutableSetOf<CtClass>()\n            val overrideOnInflateActivityFragments = mutableSetOf<CtClass>()\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = allInputClass\n                    .filter { it.isFragment() }\n                    .toSet()\n\n                override fun transform(ctClass: CtClass) {\n                    val onInflateContext: CtMethod? =\n                        try {\n                            ctClass.getDeclaredMethod(\n                                \"onInflate\",\n                                arrayOf(androidContext, androidAttributeSet, androidBundle)\n                            )\n                        } catch (e: NotFoundException) {\n                            null\n                        }\n                    val onInflateActivity: CtMethod? =\n                        try {\n                            ctClass.getDeclaredMethod(\n                                \"onInflate\",\n                                arrayOf(shadowActivity, androidAttributeSet, androidBundle)\n                            )\n                        } catch (e: NotFoundException) {\n                            null\n                        }\n                    if (onInflateContext != null) {\n                        overrideOnInflateContextFragments.add(ctClass)\n                    }\n                    if (onInflateActivity != null) {\n                        overrideOnInflateActivityFragments.add(ctClass)\n                    }\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnInflateContextFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    val onInflateContext: CtMethod = ctClass.getDeclaredMethod(\n                        \"onInflate\",\n                        arrayOf(androidContext, androidAttributeSet, androidBundle)\n                    )\n                    //原来的onInflate方法改名为onInflateShadowContext\n                    onInflateContext.name = \"onInflateShadowContext\"\n                    onInflateContext.modifiers = Modifier.setPrivate(onInflateContext.modifiers)\n\n                    //重新定义onInflate方法\n                    val newOnInflateContext = CtMethod.make(\n                        \"\"\"\n                        public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n                            Context pluginActivity = ShadowFragmentSupport.toPluginContext(context);\n                            onInflateShadowContext(pluginActivity, attrs, savedInstanceState);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n                    ctClass.addMethod(newOnInflateContext)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) =\n                    overrideOnInflateActivityFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    val onInflateActivity: CtMethod = ctClass.getDeclaredMethod(\n                        \"onInflate\",\n                        arrayOf(shadowActivity, androidAttributeSet, androidBundle)\n                    )\n                    //原来的onInflate方法改名为onInflateShadowContext\n                    onInflateActivity.name = \"onInflateShadowContext\"\n                    onInflateActivity.modifiers = Modifier.setPrivate(onInflateActivity.modifiers)\n\n                    //重新定义onInflate方法\n                    val newOnInflateContext = CtMethod.make(\n                        \"\"\"\n                        public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n                            ShadowActivity shadowActivity = (ShadowActivity)ShadowFragmentSupport.toPluginContext(activity);\n                            onInflateShadowContext(shadowActivity, attrs, savedInstanceState);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n                    ctClass.addMethod(newOnInflateContext)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) = overrideOnInflateContextFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    //定义将插件Activity还原为ContainerActivity再调用super.onInflate方法的转调方法\n                    val superOnInflate = CtMethod.make(\n                        \"\"\"\n                        private void superOnInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n                            Context pluginContainerActivity = ShadowFragmentSupport.toOriginalContext(context);\n                            super.onInflate(pluginContainerActivity, attrs, savedInstanceState);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                    )\n\n                    //将插件Fragment中对super.onAttach的调用改调到superOnAttach上\n                    val codeConverter = CodeConverter()\n                    val superOnInflateContext: CtMethod = ctClass.superclass.getMethod(\n                        \"onInflate\",\n                        \"(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V\"\n                    )\n                    codeConverter.redirectMethodCall(superOnInflateContext, superOnInflate)\n                    try {\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n\n                    /**\n                     * 一定要在super.onInflate转调到superOnInflate之后\n                     * 再把superOnInflate添加到ctClass上，避免superOnInflate中的\n                     * super.onInflate也被改成superOnInflate\n                     */\n                    ctClass.addMethod(superOnInflate)\n                }\n            })\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) =\n                    overrideOnInflateActivityFragments\n\n                override fun transform(ctClass: CtClass) {\n                    ctClass.defrost()\n                    //定义将插件Activity还原为ContainerActivity再调用super.onInflate方法的转调方法\n\n                    val superOnInflate =\n                        fixSuperOnInflateCall(ctClass) {\n                            CtMethod.make(\n                                \"\"\"\n                        private void superOnInflate(ShadowActivity shadowActivity, AttributeSet attrs, Bundle savedInstanceState) {\n                            Activity pluginContainerActivity = (Activity)ShadowFragmentSupport.toOriginalContext(shadowActivity);\n                            super.onInflate(pluginContainerActivity, attrs, savedInstanceState);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                            )\n                        }\n\n                    //将插件Fragment中对super.onAttach的调用改调到superOnAttach上\n                    val codeConverter = CodeConverter()\n                    var superOnInflateActivity: CtMethod = androidFragment.getDeclaredMethod(\n                        \"onInflate\",\n                        arrayOf(androidActivity, androidAttributeSet, androidBundle)\n                    )\n                    superOnInflateActivity =\n                        CtNewMethod.copy(superOnInflateActivity, androidFragment, null)\n                    superOnInflateActivity.methodInfo.descriptor =\n                        \"(Lcom/tencent/shadow/core/runtime/ShadowActivity;Landroid/util/AttributeSet;Landroid/os/Bundle;)V\"\n                    codeConverter.redirectMethodCall(superOnInflateActivity, superOnInflate)\n                    try {\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n\n                    /**\n                     * 一定要在super.onInflate转调到superOnInflate之后\n                     * 再把superOnInflate添加到ctClass上，避免superOnInflate中的\n                     * super.onInflate也被改成superOnInflate\n                     */\n                    ctClass.addMethod(superOnInflate)\n                }\n\n                /**\n                 * Javassist疑似有bug，当super类存在满足签名的方法时，就不会去父类的父类中查找更加准确匹配的方法了。\n                 * 导致当父类只Override了onAttach(Context)方法时，我们定义的superOnAttach方法中的\n                 * super.onAttach(Activity)调用会编译成对onAttach(Context)的调用。这与正常的Javac编译结果不一致。\n                 *\n                 * 因此，在这里如果父类没有定义onAttach(Activity)，先为它添加上，make后再移除。\n                 */\n                private fun fixSuperOnInflateCall(\n                    ctClass: CtClass,\n                    make: () -> CtMethod\n                ): CtMethod {\n                    val superclass = ctClass.superclass\n                    val needFix = try {\n                        superclass.getDeclaredMethod(\n                            \"onInflate\",\n                            arrayOf(androidActivity, androidAttributeSet, androidBundle)\n                        )\n                        false\n                    } catch (e: NotFoundException) {\n                        true\n                    }\n                    return if (needFix) {\n                        superclass.defrost()\n                        val newOnInflateActivity = CtMethod.make(\n                            \"\"\"\n                                public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {}\n                            \"\"\".trimIndent(), superclass\n                        )\n                        superclass.addMethod(newOnInflateActivity)\n                        val result = make()\n                        superclass.removeMethod(newOnInflateActivity)\n                        result\n                    } else {\n                        make()\n                    }\n                }\n            })\n        }\n        onInflateSupport()\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/InstrumentationTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.ReplaceClassName\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.CtMethod\n\nclass InstrumentationTransform : SpecificTransform() {\n    companion object {\n        const val AndroidInstrumentationClassname = \"android.app.Instrumentation\"\n        const val ShadowInstrumentationClassname =\n            \"com.tencent.shadow.core.runtime.ShadowInstrumentation\"\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        val shadowInstrumentation = mClassPool[ShadowInstrumentationClassname]\n\n        val newShadowApplicationMethods =\n            shadowInstrumentation.getDeclaredMethods(\"newShadowApplication\")\n\n        val newShadowActivityMethod = shadowInstrumentation.getDeclaredMethod(\"newShadowActivity\")\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = allInputClass\n\n            override fun transform(ctClass: CtClass) {\n                ReplaceClassName.replaceClassName(\n                    ctClass,\n                    AndroidInstrumentationClassname,\n                    ShadowInstrumentationClassname\n                )\n            }\n        })\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = allInputClass\n\n            override fun transform(ctClass: CtClass) {\n                ctClass.defrost()\n                val codeConverter = CodeConverter()\n                newShadowApplicationMethods.forEach {\n                    codeConverter.redirectMethodCall(\n                        \"newApplication\",\n                        it\n                    )\n                }\n\n                codeConverter.redirectMethodCall(\"newActivity\", newShadowActivityMethod)\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                    throw e\n                }\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/IntentServiceTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nclass IntentServiceTransform : SimpleRenameTransform(\n    mapOf(\"android.app.IntentService\" to \"com.tencent.shadow.core.runtime.ShadowIntentService\")\n)\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/KeepHostContextTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.CtMethod\nimport javassist.CtNewMethod\n\nclass KeepHostContextTransform(private val rules: Array<String>) : SpecificTransform() {\n    companion object {\n        const val ShadowContextClassName = \"com.tencent.shadow.core.runtime.ShadowContext\"\n    }\n\n    data class Rule(\n        val ctClass: CtClass, val ctMethod: CtMethod, val convertArgs: Array<Int>\n    )\n\n    private fun String.assertRuleHasOnlyOneChar(char: Char) {\n        if (this.count { it == char } != 1) {\n            throw IllegalArgumentException(\"rule:${this}中\\'$char\\'的数量不为1\")\n        }\n    }\n\n    private fun parseKeepHostContextRules(appClasses: Set<CtClass>): List<Rule> {\n        return rules.map { rule ->\n            rule.assertRuleHasOnlyOneChar('(')\n            rule.assertRuleHasOnlyOneChar(')')\n\n            val indexOfLeftParenthesis = rule.indexOf('(')\n            val indexOfRightParenthesis = rule.indexOf(')')\n\n            val classNameAndMethodNamePart = rule.substring(0, indexOfLeftParenthesis)\n            val indexOfLastDot = classNameAndMethodNamePart.indexOfLast { it == '.' }\n\n            val className = classNameAndMethodNamePart.substring(0, indexOfLastDot)\n            val methodName =\n                classNameAndMethodNamePart.substring(indexOfLastDot + 1, indexOfLeftParenthesis)\n            val methodParametersClassName =\n                rule.substring(indexOfLeftParenthesis + 1, indexOfRightParenthesis).split(',')\n            val keepSpecifying = rule.substring(indexOfRightParenthesis + 1)\n\n            val ctClass = appClasses.find {\n                it.name == className\n            } ?: throw ClassNotFoundException(\"没有找到${rule}中指定的类$className\")\n\n            val parametersCtClass = methodParametersClassName.map {\n                mClassPool.getOrNull(it)\n                    ?: throw ClassNotFoundException(\"没有找到${rule}中指定的类$it\")\n            }.toTypedArray()\n            val ctMethod = ctClass.getDeclaredMethod(methodName, parametersCtClass)\n\n            val tmp = keepSpecifying.split('$')\n            val convertArgs = tmp.subList(1, tmp.size).map { Integer.parseInt(it) }.toTypedArray()\n\n            Rule(ctClass, ctMethod, convertArgs)\n        }\n    }\n\n    private fun wrapArg(num: Int): String = \"(($ShadowContextClassName)\\$${num}).getBaseContext()\"\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        val rules = parseKeepHostContextRules(allInputClass)\n\n        for (rule in rules) {\n            val targetClass = rule.ctClass\n            val ctMethod = rule.ctMethod\n            val cloneMethod =\n                CtNewMethod.copy(ctMethod, ctMethod.name + \"_KeepHostContext\", targetClass, null)\n\n            val newBodyBuilder = StringBuilder()\n            newBodyBuilder.append(\"${ctMethod.name}(\")\n            for (i in 1..cloneMethod.parameterTypes.size) {//从1开始是因为在Javassist中$0表示this,$1表示第一个参数\n                if (i > 1) {\n                    newBodyBuilder.append(',')\n                }\n                if (i in rule.convertArgs) {\n                    newBodyBuilder.append(wrapArg(i))\n                } else {\n                    newBodyBuilder.append(\"\\$${i}\")\n                }\n            }\n            newBodyBuilder.append(\");\")\n\n            cloneMethod.setBody(newBodyBuilder.toString())\n            targetClass.addMethod(cloneMethod)\n\n            val codeConverter = CodeConverter()\n            codeConverter.redirectMethodCall(ctMethod, cloneMethod)\n\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) =\n                    filterRefClasses(allInputClass, listOf(targetClass.name))\n\n                override fun transform(ctClass: CtClass) {\n                    if (ctClass != targetClass)\n                        ctClass.instrument(codeConverter)\n                }\n            })\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/LayoutInflaterTransform.kt",
    "content": "package com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.bytecode.Descriptor\n\nclass LayoutInflaterTransform : SpecificTransform() {\n    companion object {\n        const val ShadowLayoutInflaterClassname =\n            \"com.tencent.shadow.core.runtime.ShadowLayoutInflater\"\n        const val AndroidLayoutInflaterClassname = \"android.view.LayoutInflater\"\n        const val LayoutInflaterFactoryClassname = \"android.view.LayoutInflater\\$Factory\"\n        const val LayoutInflaterFactory2Classname = \"android.view.LayoutInflater\\$Factory2\"\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n\n        val androidLayoutInflaterClass = mClassPool[AndroidLayoutInflaterClassname]\n        val shadowLayoutInflaterClass = mClassPool[ShadowLayoutInflaterClassname]\n        val layoutInflaterFactoryClass = mClassPool[LayoutInflaterFactoryClassname]\n        val layoutInflaterFactory2Class = mClassPool[LayoutInflaterFactory2Classname]\n\n        val getFactoryMethod = androidLayoutInflaterClass.getMethod(\n            \"getFactory\",\n            Descriptor.ofMethod(layoutInflaterFactoryClass, null)\n        )\n        val getFactory2Method = androidLayoutInflaterClass.getMethod(\n            \"getFactory2\",\n            Descriptor.ofMethod(layoutInflaterFactory2Class, null)\n        )\n        val getOriginalFactoryMethod = shadowLayoutInflaterClass.getMethod(\n            \"getOriginalFactory\",\n            Descriptor.ofMethod(layoutInflaterFactoryClass, arrayOf(androidLayoutInflaterClass))\n        )\n        val getOriginalFactory2Method = shadowLayoutInflaterClass.getMethod(\n            \"getOriginalFactory2\",\n            Descriptor.ofMethod(layoutInflaterFactory2Class, arrayOf(androidLayoutInflaterClass))\n        )\n\n        val codeConverter = CodeConverter()\n        codeConverter.redirectMethodCallToStatic(getFactoryMethod, getOriginalFactoryMethod)\n        codeConverter.redirectMethodCallToStatic(getFactory2Method, getOriginalFactory2Method)\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(AndroidLayoutInflaterClassname))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/PackageItemInfoTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform.ShadowTransform.Companion.SelfClassNamePlaceholder\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.*\nimport java.util.*\n\nclass PackageItemInfoTransform : SpecificTransform() {\n    companion object {\n        const val AndroidProviderInfo = \"android.content.pm.ProviderInfo\"\n        const val AndroidActivityInfo = \"android.content.pm.ActivityInfo\"\n        const val AndroidApplicationInfo = \"android.content.pm.ApplicationInfo\"\n        const val AndroidServiceInfo = \"android.content.pm.ServiceInfo\"\n        const val AndroidPackageItemInfo = \"android.content.pm.PackageItemInfo\"\n        const val ShadowAndroidPackageItemInfo =\n            \"com.tencent.shadow.core.runtime.ShadowPackageItemInfo\"\n    }\n\n    private fun setup(\n        targetClassNames: Array<String>,\n        targetMethodName: Array<String>,\n        redirectRule: Pair<String, String>\n    ) {\n        val targetMethods = getTargetMethods(targetClassNames, targetMethodName)\n        targetMethods.forEach { targetMethod ->\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) =\n                    filterRefClasses(\n                        allInputClass,\n                        targetClassNames.asList()\n                    ).filter { matchMethodCallInClass(targetMethod, it) }.toSet()\n\n                override fun transform(ctClass: CtClass) {\n                    System.out.println(ctClass.name + \" matchMethodCallInClass :\" + targetMethod.methodInfo.name + \"  =================\")\n                    try {\n                        val targetClass = mClassPool[redirectRule.first]\n                        val redirectClassName = redirectRule.second\n                        val parameterTypes: Array<CtClass> =\n                            Array(targetMethod.parameterTypes.size + 1) { index ->\n                                if (index == 0) {\n                                    targetClass\n                                } else {\n                                    targetMethod.parameterTypes[index - 1]\n                                }\n                            }\n                        val newMethod = CtNewMethod.make(\n                            Modifier.PUBLIC or Modifier.STATIC,\n                            targetMethod.returnType,\n                            targetMethod.name + \"_shadow\",\n                            parameterTypes,\n                            targetMethod.exceptionTypes,\n                            null,\n                            ctClass\n                        )\n                        val newBodyBuilder = StringBuilder()\n                        newBodyBuilder\n                            .append(\"return \")\n                            .append(redirectClassName)\n                            .append(\".\")\n                            .append(targetMethod.methodInfo.name)\n                            .append(\"(\")\n                            .append(SelfClassNamePlaceholder)\n                            .append(\".class.getClassLoader(),\")\n                        for (i in 1..newMethod.parameterTypes.size) {\n                            if (i > 1) {\n                                newBodyBuilder.append(',')\n                            }\n                            newBodyBuilder.append(\"\\$${i}\")\n                        }\n                        newBodyBuilder.append(\");\")\n\n                        newMethod.setBody(newBodyBuilder.toString())\n                        ctClass.addMethod(newMethod)\n                        ctClass.replaceClassName(SelfClassNamePlaceholder, ctClass.name)\n                        val codeConverter = CodeConverter()\n                        codeConverter.redirectMethodCallToStatic(targetMethod, newMethod)\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n                }\n            })\n        }\n    }\n\n    /**\n     * 查找目标class对象的目标method\n     */\n    private fun getTargetMethods(\n        targetClassNames: Array<String>,\n        targetMethodName: Array<String>\n    ): List<CtMethod> {\n        val method_targets = ArrayList<CtMethod>()\n        for (targetClassName in targetClassNames) {\n            val methods = mClassPool[targetClassName].methods\n            method_targets.addAll(methods.filter { targetMethodName.contains(it.name) })\n        }\n        return method_targets\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        setup(\n            arrayOf(\n                AndroidProviderInfo,\n                AndroidServiceInfo,\n                AndroidApplicationInfo,\n                AndroidActivityInfo\n            ),\n            arrayOf(\"loadXmlMetaData\"),\n            AndroidPackageItemInfo to ShadowAndroidPackageItemInfo\n        )\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/PackageManagerTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform.ShadowTransform.Companion.SelfClassNamePlaceholder\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.*\n\nclass PackageManagerTransform : SpecificTransform() {\n    companion object {\n        const val AndroidPackageManagerClassname = \"android.content.pm.PackageManager\"\n        const val ShadowAndroidPackageManagerClassname =\n            \"com.tencent.shadow.core.runtime.PackageManagerInvokeRedirect\"\n    }\n\n    private fun setupPackageManagerTransform(targetMethodName: Array<String>) {\n        val targetMethods =\n            getTargetMethods(arrayOf(AndroidPackageManagerClassname), targetMethodName)\n        targetMethods.forEach { targetMethod ->\n            newStep(object : TransformStep {\n                override fun filter(allInputClass: Set<CtClass>) =\n                    filterRefClasses(\n                        allInputClass,\n                        listOf(AndroidPackageManagerClassname)\n                    ).filter { matchMethodCallInClass(targetMethod, it) }.toSet()\n\n                override fun transform(ctClass: CtClass) {\n                    try {\n                        val targetClass = mClassPool[AndroidPackageManagerClassname]\n                        val parameterTypes: Array<CtClass> =\n                            Array(targetMethod.parameterTypes.size + 1) { index ->\n                                if (index == 0) {\n                                    targetClass\n                                } else {\n                                    targetMethod.parameterTypes[index - 1]\n                                }\n                            }\n                        val newMethod = CtNewMethod.make(\n                            Modifier.PUBLIC or Modifier.STATIC,\n                            targetMethod.returnType,\n                            targetMethod.name + \"_shadow\",\n                            parameterTypes,\n                            targetMethod.exceptionTypes,\n                            null,\n                            ctClass\n                        )\n                        val newBodyBuilder = StringBuilder()\n                        newBodyBuilder\n                            .append(\"return \")\n                            .append(ShadowAndroidPackageManagerClassname)\n                            .append(\".\")\n                            .append(targetMethod.methodInfo.name)\n                            .append(\"(\")\n                            .append(SelfClassNamePlaceholder)\n                            .append(\".class.getClassLoader(),\")\n                        //下面放弃第0个和第1个参数，第0个是this，\n                        //第1个是redirectMethodCallToStaticMethodCall时原本被调用的PackageManager对象。\n                        for (i in 2..newMethod.parameterTypes.size) {\n                            if (i > 2) {\n                                newBodyBuilder.append(',')\n                            }\n                            newBodyBuilder.append(\"\\$${i}\")\n                        }\n                        newBodyBuilder.append(\");\")\n\n                        newMethod.setBody(newBodyBuilder.toString())\n                        ctClass.addMethod(newMethod)\n                        ctClass.replaceClassName(SelfClassNamePlaceholder, ctClass.name)\n                        val codeConverter = CodeConverter()\n                        codeConverter.redirectMethodCallToStatic(targetMethod, newMethod)\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n                }\n            })\n        }\n    }\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        setupPackageManagerTransform(\n            arrayOf(\n                \"getApplicationInfo\",\n                \"getActivityInfo\",\n                \"getServiceInfo\",\n                \"getProviderInfo\",\n                \"getPackageInfo\",\n                \"resolveContentProvider\",\n                \"queryContentProviders\",\n                \"resolveActivity\",\n                \"resolveService\",\n            )\n        )\n    }\n\n    /**\n     * 查找目标class对象的目标method\n     */\n    private fun getTargetMethods(\n        targetClassNames: Array<String>,\n        targetMethodName: Array<String>\n    ): List<CtMethod> {\n        val method_targets = ArrayList<CtMethod>()\n        for (targetClassName in targetClassNames) {\n            val methods = mClassPool[targetClassName].methods\n            method_targets.addAll(methods.filter { targetMethodName.contains(it.name) })\n        }\n        return method_targets\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/PendingIntentTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\n\nclass PendingIntentTransform : SpecificTransform() {\n\n    companion object {\n        const val AndroidPendingIntentClassname = \"android.app.PendingIntent\"\n        const val ShadowPendingIntentClassname =\n            \"com.tencent.shadow.core.runtime.ShadowPendingIntent\"\n    }\n\n    val codeConverter = CodeConverter()\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        val pendingIntentMethod = mClassPool[AndroidPendingIntentClassname].methods!!\n        val shadowPendingIntentMethod = mClassPool[ShadowPendingIntentClassname].methods!!\n\n        val method_getPengdingIntent =\n            pendingIntentMethod.filter { it.name == \"getService\" || it.name == \"getActivity\" }\n        val shadow_method_getPengdingIntent =\n            shadowPendingIntentMethod.filter { it.name == \"getService\" || it.name == \"getActivity\" }!!\n\n        for (ctAndroidMethod in method_getPengdingIntent) {\n            for (ctShadowMedthod in shadow_method_getPengdingIntent) {\n                if (ctShadowMedthod.methodInfo.name == ctAndroidMethod.methodInfo.name && ctAndroidMethod.methodInfo.descriptor == ctShadowMedthod.methodInfo.descriptor) {\n                    codeConverter.redirectMethodCall(ctAndroidMethod, ctShadowMedthod)\n                }\n            }\n        }\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(AndroidPendingIntentClassname))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ReceiverSupportTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CodeConverter\nimport javassist.CtClass\nimport javassist.CtMethod\nimport javassist.Modifier\nimport javassist.NotFoundException\nimport javassist.compiler.Javac.CtFieldWithInit\n\n/**\n * 系统回调BroadcastReceiver的onReceive(Context context, Intent intent)方法时：\n * 1. 传回的context是宿主的，需要修改为插件的。\n * 2. intent的ExtrasClassLoader是宿主的，需要改为插件的。\n *\n * 如果是系统类的BroadcastReceiver，它也不会和插件的context或classloader有什么联系，\n * 所以我们只需要修改插件代码中的BroadcastReceiver。\n *\n * 把原本插件的onReceive方法改个名字，再统一添加一个onReceive方法。\n * 在新增的onReceive方法中修改收到的系统回调参数，再转调被改名了的原本插件的onReceive方法。\n */\nclass ReceiverSupportTransform : SpecificTransform() {\n\n    companion object {\n        const val AndroidBroadcastReceiverClassname = \"android.content.BroadcastReceiver\"\n        const val AndroidContextClassname = \"android.content.Context\"\n        const val AndroidIntentClassname = \"android.content.Intent\"\n    }\n\n    private fun CtClass.isReceiver(): Boolean = isClassOf(AndroidBroadcastReceiverClassname)\n\n    override fun setup(allInputClass: Set<CtClass>) {\n        mClassPool.importPackage(\"android.content\")\n        mClassPool.importPackage(\"com.tencent.shadow.core.runtime\")\n\n        val androidContext = mClassPool[AndroidContextClassname]\n        val androidIntent = mClassPool[AndroidIntentClassname]\n\n        /**\n         * 收集覆盖了onReceive方法的Receiver作为修改目标\n         */\n        val targetReceivers = mutableSetOf<CtClass>()\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = allInputClass\n                .filter { it.isReceiver() }\n                .toSet()\n\n            override fun transform(ctClass: CtClass) {\n                val onReceiveMethod: CtMethod? =\n                    try {\n                        ctClass.getDeclaredMethod(\n                            \"onReceive\",\n                            arrayOf(androidContext, androidIntent)\n                        )\n                    } catch (e: NotFoundException) {\n                        null\n                    }\n                if (onReceiveMethod != null && !Modifier.isVolatile(onReceiveMethod.modifiers)) {\n                    targetReceivers.add(ctClass)\n                }\n            }\n        })\n\n        /**\n         * 对原本的onReceive方法改名，并添加新的onReceive方法。\n         */\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) = targetReceivers\n\n            override fun transform(ctClass: CtClass) {\n                ctClass.defrost()\n\n                // 改名\n                val originalOnReceiveMethod: CtMethod =\n                    ctClass.getDeclaredMethod(\n                        \"onReceive\",\n                        arrayOf(androidContext, androidIntent)\n                    )\n                originalOnReceiveMethod.name = \"onReceiveShadowContext\"\n                originalOnReceiveMethod.modifiers =\n                    Modifier.setPrivate(originalOnReceiveMethod.modifiers)\n\n                // 声明两个域变量保存onReceive收到的原始参数，供调用super方法时使用。\n                // the compiler embedded in Javassist does not support generics\n                arrayOf(\n                    CtFieldWithInit.make(\n                        \"ThreadLocal originalOnReceiveContext = new ThreadLocal();\",\n                        ctClass\n                    ),\n                    CtFieldWithInit.make(\n                        \"ThreadLocal originalOnReceiveIntent = new ThreadLocal();\",\n                        ctClass\n                    ),\n                ).forEach {\n                    ctClass.addField(it)\n                }\n\n                // 添加新onReceive方法\n                val newOnReceiveMethod = CtMethod.make(\n                    \"\"\"\n                        public void onReceive(Context context, Intent intent) {\n                            try{\n                                //保存收到的参数\n                                originalOnReceiveContext.set(context);\n                                originalOnReceiveIntent.set(intent);\n                                \n                                //通过当前插件类ClassLoader找到相关的插件Application\n                                ClassLoader cl = this.getClass().getClassLoader();\n                                PluginPartInfo info = PluginPartInfoManager.getPluginInfo(cl);\n                                Context shadowContext = info.application;\n                                Intent intentCopy = new Intent(intent);//不修改原本的intent\n                                intentCopy.setExtrasClassLoader(cl);\n                                \n                                //调用原本的onReceive方法\n                                onReceiveShadowContext(shadowContext, intentCopy);\n                            }finally {\n                                originalOnReceiveContext.remove();\n                                originalOnReceiveIntent.remove();\n                            }\n                        }\n                    \"\"\".trimIndent(), ctClass\n                )\n                ctClass.addMethod(newOnReceiveMethod)\n\n                // 定义superOnReceiveMethod方法\n                val newSuperMethod = CtMethod.make(\n                    \"\"\"\n                        private void superOnReceive(Context context, Intent intent) {\n                            context = (Context)originalOnReceiveContext.get();\n                            intent = (Intent)originalOnReceiveIntent.get();\n                            super.onReceive(context, intent);\n                        }\n                    \"\"\".trimIndent(), ctClass\n                )\n\n                //转调super.onReceive方法\n                val superMethod: CtMethod =\n                    ctClass.superclass.getMethod(\n                        \"onReceive\",\n                        \"(Landroid/content/Context;Landroid/content/Intent;)V\"\n                    )\n\n                if (Modifier.isAbstract(superMethod.modifiers).not()) {\n                    val codeConverter = CodeConverter()\n                    codeConverter.redirectMethodCall(superMethod, newSuperMethod)\n                    try {\n                        ctClass.instrument(codeConverter)\n                    } catch (e: Exception) {\n                        System.err.println(\"处理\" + ctClass.name + \"时出错:\" + e)\n                        throw e\n                    }\n                    ctClass.addMethod(newSuperMethod)\n                }\n            }\n\n        })\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/ServiceTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nclass ServiceTransform : SimpleRenameTransform(\n    mapOf(\n        \"android.app.Service\"\n                to \"com.tencent.shadow.core.runtime.ShadowService\"\n    )\n)\n"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/SimpleRenameTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.ReplaceClassName\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CtClass\n\nopen class SimpleRenameTransform(private val fromToMap: Map<String, String>) : SpecificTransform() {\n    final override fun setup(allInputClass: Set<CtClass>) {\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, fromToMap.keys.toList())\n\n            override fun transform(ctClass: CtClass) {\n                fromToMap.forEach {\n                    ReplaceClassName.replaceClassName(ctClass, it.key, it.value)\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/main/kotlin/com/tencent/shadow/core/transform/specific/WebViewTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.SpecificTransform\nimport com.tencent.shadow.core.transform_kit.TransformStep\nimport javassist.CtClass\nimport javassist.EnhancedCodeConverter\n\nclass WebViewTransform : SpecificTransform() {\n    companion object {\n        const val AndroidWebViewClassname = \"android.webkit.WebView\"\n        const val ShadowWebViewClassname = \"com.tencent.shadow.core.runtime.ShadowWebView\"\n    }\n\n    val codeConverter = EnhancedCodeConverter()\n    override fun setup(allInputClass: Set<CtClass>) {\n        codeConverter.replaceNew(\n            mClassPool[AndroidWebViewClassname],\n            mClassPool[ShadowWebViewClassname]\n        )\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(AndroidWebViewClassname))\n\n            override fun transform(ctClass: CtClass) {\n                if (ctClass.superclass.name == AndroidWebViewClassname) {\n                    ctClass.classFile.superclass = ShadowWebViewClassname\n                }\n            }\n        })\n\n        newStep(object : TransformStep {\n            override fun filter(allInputClass: Set<CtClass>) =\n                filterRefClasses(allInputClass, listOf(AndroidWebViewClassname))\n\n            override fun transform(ctClass: CtClass) {\n                try {\n                    ctClass.instrument(codeConverter)\n                } catch (e: Exception) {\n                    System.err.println(\"处理\" + ctClass.name + \"时出错\")\n                    throw e\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/app/Activity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.app;\n\npublic class Activity {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/app/Application.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.app;\n\npublic class Application {\n\n    public interface ActivityLifecycleCallbacks {\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/app/Fragment.java",
    "content": "package android.app;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.ShadowActivity;\n\n/**\n * 因为在TransformManager中ActivityTransform是在FragmentSupportTransform之前进行的，\n * 所以FragmentSupportTransform运行时，Activity已经都变成ShadowActivity了，\n * 所以这个Mock类定义的方法中Activity已经是ShadowActivity了。\n */\npublic class Fragment {\n    final public ShadowActivity getActivity() {\n        return null;\n    }\n\n    public Context getContext() {\n        return null;\n    }\n\n    final public Object getHost() {\n        return null;\n    }\n\n    public void startActivity(Intent intent) {\n\n    }\n\n    public void startActivity(Intent intent, Bundle options) {\n\n    }\n\n    public void startActivityForResult(Intent intent, int requestCode) {\n\n    }\n\n    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {\n\n    }\n\n    public void onAttach(Context context) {\n\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/app/Instrumentation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.app;\n\nimport android.content.Intent;\n\npublic class Instrumentation {\n\n    public static class ActivityResult {\n        public ActivityResult(int code, Intent intent) {\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/app/Service.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.app;\n\npublic class Service {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/BroadcastReceiver.java",
    "content": "package android.content;\n\npublic abstract class BroadcastReceiver {\n    public abstract void onReceive(Context context, Intent intent);\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/ComponentName.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content;\n\npublic class ComponentName {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/Context.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content;\n\npublic class Context {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/Intent.java",
    "content": "package android.content;\n\npublic class Intent {\n    public Intent() {\n    }\n\n    public Intent(Intent intent) {\n    }\n\n    public void setExtrasClassLoader(ClassLoader loader) {\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/ActivityInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\npublic class ActivityInfo extends PackageItemInfo {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/ApplicationInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\n\npublic class ApplicationInfo extends PackageItemInfo {\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/PackageInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\npublic class PackageInfo {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/PackageItemInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\nimport android.content.res.XmlResourceParser;\n\npublic class PackageItemInfo {\n\n    public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/PackageManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\nimport android.content.ComponentName;\n\npublic class PackageManager {\n\n\n    public ApplicationInfo getApplicationInfo(String packageName, int flag) {\n        return null;\n    }\n\n\n    public ActivityInfo getActivityInfo(ComponentName component, int flags) {\n        return null;\n    }\n\n\n    public PackageInfo getPackageInfo(String packageName, int flags) {\n        return null;\n    }\n\n\n    public ProviderInfo resolveContentProvider(String name, int flags) {\n\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/ProviderInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\npublic class ProviderInfo extends PackageItemInfo {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/pm/ServiceInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.pm;\n\npublic class ServiceInfo extends PackageItemInfo {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/content/res/XmlResourceParser.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.content.res;\n\npublic interface XmlResourceParser {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/os/Bundle.java",
    "content": "package android.os;\n\npublic class Bundle {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/os/IBinder.java",
    "content": "package android.os;\n\npublic interface IBinder {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/util/AttributeSet.java",
    "content": "package android.util;\n\npublic class AttributeSet {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/android/webkit/WebView.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage android.webkit;\n\nimport android.content.Context;\n\npublic class WebView {\n    public WebView(Context context) {\n\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/PackageManagerInvokeRedirect.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.ComponentName;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.ProviderInfo;\n\npublic class PackageManagerInvokeRedirect {\n\n\n    public static ApplicationInfo getApplicationInfo(ClassLoader classLoader, String packageName, int flags) {\n        return null;\n    }\n\n\n    public static ActivityInfo getActivityInfo(ClassLoader classLoader, ComponentName component, int flags) {\n        return null;\n    }\n\n\n    public static PackageInfo getPackageInfo(ClassLoader classLoader, String packageName, int flags) {\n        return null;\n    }\n\n\n    public static ProviderInfo resolveContentProvider(ClassLoader classLoader, String name, int flags) {\n\n        return null;\n    }\n\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/PluginPartInfo.java",
    "content": "package com.tencent.shadow.core.runtime;\n\npublic class PluginPartInfo {\n    public ShadowApplication application = new ShadowApplication();\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/PluginPartInfoManager.java",
    "content": "package com.tencent.shadow.core.runtime;\n\npublic class PluginPartInfoManager {\n    public static PluginPartInfo getPluginInfo(ClassLoader classLoader) {\n        return new PluginPartInfo();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowActivity.java",
    "content": "package com.tencent.shadow.core.runtime;\n\npublic class ShadowActivity {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowApplication.java",
    "content": "package com.tencent.shadow.core.runtime;\n\npublic class ShadowApplication extends ShadowContext {\n\n    public void onCreate() {\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowContext.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\n\npublic class ShadowContext extends Context {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowFragmentSupport.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\n\npublic class ShadowFragmentSupport {\n\n    public static ShadowActivity fragmentGetActivity(Fragment fragment) {\n        return null;\n    }\n\n    public static Context fragmentGetContext(Fragment fragment) {\n        return null;\n    }\n\n    public static Object fragmentGetHost(Fragment fragment) {\n        return null;\n    }\n\n    public void fragmentStartActivity(Fragment fragment, Intent intent) {\n    }\n\n    public void fragmentStartActivity(Fragment fragment, Intent intent, Bundle options) {\n    }\n\n    public static void fragmentStartActivityForResult(Fragment fragment, Intent intent, int requestCode) {\n    }\n\n    public static void fragmentStartActivityForResult(Fragment fragment, Intent intent, int requestCode, Bundle options) {\n    }\n\n    public static Context toPluginContext(Context pluginContainerActivity) {\n        return null;\n    }\n\n    public static Context toOriginalContext(Context pluginActivity) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowInstrumentation.java",
    "content": "package com.tencent.shadow.core.runtime;\n\nimport android.app.Fragment;\nimport android.app.Instrumentation;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.IBinder;\n\npublic class ShadowInstrumentation {\n    public void callActivityOnDestroy(ShadowActivity activity) {\n    }\n\n    static public ShadowApplication newShadowApplication(Class<?> clazz, Context context)\n            throws InstantiationException, IllegalAccessException,\n            ClassNotFoundException {\n        return null;\n    }\n\n    public ShadowApplication newApplication(ClassLoader cl, String className, Context context)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return null;\n    }\n\n    public ShadowActivity newShadowActivity(ClassLoader cl, String className, Intent intent)\n            throws InstantiationException, IllegalAccessException, ClassNotFoundException {\n        return (ShadowActivity) cl.loadClass(className).newInstance();\n    }\n\n    public void callApplicationOnCreate(ShadowApplication app) {\n        app.onCreate();\n    }\n\n    public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, ShadowActivity target, Intent intent, int requestCode) {\n        return new Instrumentation.ActivityResult(requestCode, intent);\n    }\n\n    public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, ShadowActivity target, Intent intent, int requestCode, Bundle options) {\n        return new Instrumentation.ActivityResult(requestCode, intent);\n    }\n\n    public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {\n        return new Instrumentation.ActivityResult(requestCode, intent);\n    }\n\n    public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {\n        return new Instrumentation.ActivityResult(requestCode, intent);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/com/tencent/shadow/core/runtime/ShadowWebView.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.runtime;\n\nimport android.content.Context;\nimport android.webkit.WebView;\n\npublic class ShadowWebView extends WebView {\n\n    public ShadowWebView(Context context) {\n        super(context);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/EggReceiver.java",
    "content": "package test;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport java.util.List;\n\nabstract class EggReceiver extends BroadcastReceiver {\n    List<String> log;\n\n    EggReceiver(List<String> log) {\n        this.log = log;\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        log.add(\"EggReceiver onReceive\");\n    }\n\n    public static class FoxReceiver extends EggReceiver {\n        FoxReceiver(List<String> log) {\n            super(log);\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.app.Activity;\n\npublic class TestActivity extends Activity {\n\n    Activity foo(Activity activity) {\n        activity.toString();\n        return activity;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestActivityLifecycleCallbacks.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.app.Application;\n\npublic class TestActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {\n\n    Application.ActivityLifecycleCallbacks get() {\n        System.out.println(\"get ActivityLifecycleCallbacks\");\n        return this;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.app.Application;\n\npublic class TestApplication extends Application {\n\n    Application get() {\n        System.out.println(\"get Application\");\n        return this;\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestInstrumentation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.app.Instrumentation;\n\npublic class TestInstrumentation extends Instrumentation {\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestPackageManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.content.ComponentName;\nimport android.content.pm.PackageManager;\n\npublic class TestPackageManager {\n\n    void test1() {\n        PackageManager packageManager = new PackageManager();\n\n        packageManager.getApplicationInfo(\"test\", 0);\n        packageManager.getActivityInfo(new ComponentName(), 0);\n    }\n\n\n    void test2() {\n        new Inner() {\n            @Override\n            void run() {\n                PackageManager packageManager = new PackageManager();\n\n                packageManager.getApplicationInfo(\"test\", 0);\n                packageManager.getActivityInfo(new ComponentName(), 0);\n\n                new Inner() {\n                    @Override\n                    void run() {\n                        PackageManager packageManager = new PackageManager();\n\n                        packageManager.getApplicationInfo(\"test\", 0);\n                        packageManager.getActivityInfo(new ComponentName(), 0);\n\n                    }\n                };\n            }\n        };\n    }\n\n\n    class Inner {\n        void run() {\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestReceiver.java",
    "content": "package test;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\nimport java.util.List;\n\n/**\n * 直接继承\n * <p>\n * 需要修改，但不用管super调用。\n */\nclass AceReceiver extends BroadcastReceiver {\n    List<String> log;\n\n    AceReceiver(List<String> log) {\n        this.log = log;\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        log.add(\"AceReceiver onReceive\");\n    }\n}\n\n/**\n * 间接继承，不调用父类方法\n * <p>\n * 需要修改，和直接继承一样。\n */\nclass BarReceiver extends AceReceiver {\n    List<String> log;\n\n    BarReceiver(List<String> log) {\n        super(log);\n        this.log = log;\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        // do not call super\n        log.add(\"BarReceiver onReceive\");\n    }\n}\n\n/**\n * 间接继承，调用父类方法\n * <p>\n * 需要修改，和直接继承一样。\n * <p>\n * 额外的，需要把super调用改到superOnReceive上。不能直接调用super的被修改后的方法，\n * 因为super可能是个系统类，我们不会修改它。所以让superOnReceive用原本的参数调用super原本的方法。\n */\nclass CatReceiver extends BarReceiver {\n    List<String> log;\n\n    CatReceiver(List<String> log) {\n        super(log);\n        this.log = log;\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        log.add(\"CatReceiver onReceive enter\");\n        super.onReceive(context, intent);\n        log.add(\"CatReceiver onReceive leave\");\n    }\n}\n\n/**\n * 间接继承，不覆盖方法\n * <p>\n * 这种不用修改，它不会自己处理onReceive收到的参数。\n */\nclass DogReceiver extends CatReceiver {\n    List<String> log;\n\n    DogReceiver(List<String> log) {\n        super(log);\n        this.log = log;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestService.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.app.Service;\n\npublic class TestService extends Service {\n\n    Service getService() {\n        System.out.println(\"getService\");\n        return this;\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/TestWebView.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\nimport android.content.Context;\nimport android.webkit.WebView;\n\npublic class TestWebView extends WebView {\n\n    public TestWebView(Context context) {\n        super(context);\n    }\n\n    WebView getWebView() {\n        System.out.println(\"getWebView\");\n        return this;\n    }\n\n    void testNewWebView() {\n        WebView webView = new WebView(new Context());\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/fragment/TestFragment.java",
    "content": "package test.fragment;\n\nimport android.app.Fragment;\nimport android.content.Context;\n\npublic class TestFragment extends Fragment {\n\n    @Override\n    public void onAttach(Context context) {\n        System.out.println(\"before super.onAttach\" + context);\n        super.onAttach(context);\n        System.out.println(\"after super.onAttach\" + context);\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/fragment/UseGetActivityFragment.java",
    "content": "package test.fragment;\n\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.ShadowActivity;\n\npublic class UseGetActivityFragment {\n\n    ShadowActivity test(TestFragment fragment) {\n        fragment.startActivity(new Intent());\n        fragment.startActivity(new Intent(), new Bundle());\n        return fragment.getActivity();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/fragment/UseStartActivityForResultFragment.java",
    "content": "package test.fragment;\n\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.ShadowActivity;\n\npublic class UseStartActivityForResultFragment {\n\n    ShadowActivity test(TestFragment fragment) {\n        fragment.startActivityForResult(new Intent(), 1);\n        fragment.startActivityForResult(new Intent(), 1, new Bundle());\n        return fragment.getActivity();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/java/test/fragment/UseStartActivityFragment.java",
    "content": "package test.fragment;\n\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.runtime.ShadowActivity;\n\npublic class UseStartActivityFragment {\n\n    ShadowActivity test(TestFragment fragment) {\n        fragment.startActivity(new Intent());\n        fragment.startActivity(new Intent(), new Bundle());\n        return fragment.getActivity();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/ActivityLifecycleCallbacksTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.NotFoundException\nimport org.junit.Assert\nimport org.junit.Test\n\nclass ActivityLifecycleCallbacksTransformTest : AbstractTransformTest() {\n\n    @Test\n    fun testApplicationTransform() {\n        val callbackTargetClass = sLoader[\"test.TestActivityLifecycleCallbacks\"]\n\n        val allInputClass = setOf(callbackTargetClass)\n\n        val applicationTransform = ApplicationTransform()\n        applicationTransform.mClassPool = sLoader\n        applicationTransform.setup(allInputClass)\n\n        applicationTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                Assert.assertTrue(\n                    \"transform前应该能找到\" + \"get\" + \"方法\",\n                    try {\n                        it.getMethod(\n                            \"get\",\n                            \"()Landroid/app/Application\\$ActivityLifecycleCallbacks;\"\n                        )\n                        true\n                    } catch (e: NotFoundException) {\n                        false\n                    }\n                )\n\n                transform.transform(it)\n            }\n        }\n\n        allInputClass.forEach {\n            Assert.assertTrue(\n                \"transform后应该能找不到\" + \"get\" + \"方法\",\n                try {\n                    it.getMethod(\"get\", \"()Landroid/app/Application\\$ActivityLifecycleCallbacks;\")\n                    false\n                } catch (e: NotFoundException) {\n                    true\n                }\n            )\n\n            Assert.assertTrue(\n                \"transform后应该能找到新的\" + \"get\" + \"方法\",\n                try {\n                    it.getMethod(\n                        \"get\",\n                        \"()Lcom/tencent/shadow/core/runtime/ShadowActivityLifecycleCallbacks;\"\n                    )\n                    true\n                } catch (e: NotFoundException) {\n                    false\n                }\n            )\n\n            Assert.assertEquals(\n                \"ActivityLifecycleCallbacks接口应该都变为了ShadowActivityLifecycleCallbacks\",\n                \"com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks\",\n                it.classFile.interfaces[0]\n            )\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/ActivityTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport org.junit.Test\n\nclass ActivityTransformTest : SimpleRenameTransformTest(\n    ActivityTransform(), arrayOf(\"test.TestActivity\"),\n    \"foo\", \"com.tencent.shadow.core.runtime.ShadowActivity\",\n    mapOf(\n        \"(Landroid/app/Activity;)Landroid/app/Activity;\"\n                to \"(Lcom/tencent/shadow/core/runtime/ShadowActivity;)Lcom/tencent/shadow/core/runtime/ShadowActivity;\"\n    )\n) {\n\n    @Test\n    fun testActivityTransform() {\n        doTest()\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/ApplicationTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport org.junit.Test\n\nclass ApplicationTransformTest : SimpleRenameTransformTest(\n    ApplicationTransform(), arrayOf(\"test.TestApplication\"),\n    \"get\", \"com.tencent.shadow.core.runtime.ShadowApplication\",\n    mapOf(\"()Landroid/app/Application;\" to \"()Lcom/tencent/shadow/core/runtime/ShadowApplication;\")\n) {\n\n    @Test\n    fun testApplicationTransform() {\n        doTest()\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/FragmentSupportTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.CtClass\nimport javassist.NotFoundException\nimport org.junit.Assert\nimport org.junit.Test\n\n/**\n * ./gradlew -p projects/sdk/core :transform:test --tests com.tencent.shadow.core.transform.specific.FragmentSupportTransformTest\n */\nclass FragmentSupportTransformTest : AbstractTransformTest() {\n\n    companion object {\n        const val ShadowFragmentSupportClassName =\n            \"com.tencent.shadow.core.runtime.ShadowFragmentSupport\"\n        const val ShadowActivitySig = \"Lcom/tencent/shadow/core/runtime/ShadowActivity;\"\n        const val TestFragmentSig = \"Ltest/fragment/TestFragment;\"\n        const val FragmentSig = \"Landroid/app/Fragment;\"\n        const val IntentSig = \"Landroid/content/Intent;\"\n        const val BundleSig = \"Landroid/os/Bundle;\"\n    }\n\n    val shadowFragmentSupportClazz = sLoader[ShadowFragmentSupportClassName]\n    val fragmentGetActivity = shadowFragmentSupportClazz.getMethod(\n        \"fragmentGetActivity\",\n        \"($FragmentSig)$ShadowActivitySig\"\n    )\n    val fragmentStartActivity1 =\n        shadowFragmentSupportClazz.getMethod(\"fragmentStartActivity\", \"($FragmentSig$IntentSig)V\")\n    val fragmentStartActivity2 = shadowFragmentSupportClazz.getMethod(\n        \"fragmentStartActivity\",\n        \"($FragmentSig$IntentSig$BundleSig)V\"\n    )\n    val fragmentStartActivityForResult1 = shadowFragmentSupportClazz.getMethod(\n        \"fragmentStartActivityForResult\",\n        \"($FragmentSig${IntentSig}I)V\"\n    )\n    val fragmentStartActivityForResult2 = shadowFragmentSupportClazz.getMethod(\n        \"fragmentStartActivityForResult\",\n        \"($FragmentSig${IntentSig}I$BundleSig)V\"\n    )\n\n    private fun transform(clazz: CtClass) {\n        val transform = FragmentSupportTransform()\n        transform.mClassPool = sLoader\n\n        val allInputClass = setOf(\n            clazz\n        )\n        transform.setup(allInputClass)\n\n        transform.list.forEach { step ->\n            step.filter(allInputClass).forEach {\n                step.transform(it)\n                it.writeFile(WRITE_FILE_DIR)\n            }\n        }\n    }\n\n\n    @Test\n    fun fragmentGetActivity() {\n        val name = \"test.fragment.UseGetActivityFragment\"\n        transform(sLoader[name])\n\n        val transformedClass = dLoader.get(name)\n        try {\n            transformedClass.getMethod(\"test\", \"($TestFragmentSig)$ShadowActivitySig\")\n        } catch (e: Exception) {\n            Assert.fail(\"找不到正确的test方法\")\n        }\n\n        Assert.assertTrue(\n            \"${fragmentGetActivity}调用应该可以找到\",\n            matchMethodCallInClass(fragmentGetActivity, transformedClass)\n        )\n    }\n\n    @Test\n    fun fragmentStartActivity() {\n        val name = \"test.fragment.UseStartActivityFragment\"\n        transform(sLoader[name])\n\n        val transformedClass = dLoader.get(name)\n        try {\n            transformedClass.getMethod(\"test\", \"($TestFragmentSig)$ShadowActivitySig\")\n        } catch (e: Exception) {\n            Assert.fail(\"找不到正确的test方法\")\n        }\n\n        Assert.assertTrue(\n            \"${fragmentStartActivity1}调用应该可以找到\",\n            matchMethodCallInClass(fragmentStartActivity1, transformedClass)\n        )\n        Assert.assertTrue(\n            \"${fragmentStartActivity2}调用应该可以找到\",\n            matchMethodCallInClass(fragmentStartActivity2, transformedClass)\n        )\n    }\n\n    @Test\n    fun fragmentStartActivityForResult() {\n        val name = \"test.fragment.UseStartActivityForResultFragment\"\n        transform(sLoader[name])\n\n        val transformedClass = dLoader.get(name)\n        try {\n            transformedClass.getMethod(\"test\", \"($TestFragmentSig)$ShadowActivitySig\")\n        } catch (e: Exception) {\n            Assert.fail(\"找不到正确的test方法\")\n        }\n\n        Assert.assertTrue(\n            \"${fragmentStartActivityForResult1}调用应该可以找到\",\n            matchMethodCallInClass(fragmentStartActivityForResult1, transformedClass)\n        )\n        Assert.assertTrue(\n            \"${fragmentStartActivityForResult2}调用应该可以找到\",\n            matchMethodCallInClass(fragmentStartActivityForResult2, transformedClass)\n        )\n    }\n\n    @Test\n    fun attachContext() {\n        val name = \"test.fragment.TestFragment\"\n        val transform = FragmentSupportTransform()\n        transform.mClassPool = sLoader\n\n        val allInputClass = setOf(\n            sLoader[name]\n        )\n        transform.setup(allInputClass)\n\n        transform.list.forEach { step ->\n            step.filter(allInputClass).forEach {\n                step.transform(it)\n                it.writeFile(WRITE_FILE_DIR)\n            }\n        }\n\n        val transformedClass = dLoader.get(name)\n        try {\n            transformedClass.getDeclaredMethod(\"onAttach\")\n        } catch (e: NotFoundException) {\n            Assert.fail(\"找不到onAttach\")\n        }\n\n        try {\n            transformedClass.getDeclaredMethod(\"onAttachShadowContext\")\n        } catch (e: NotFoundException) {\n            Assert.fail(\"找不到onAttachShadowContext\")\n        }\n\n        try {\n            transformedClass.getDeclaredMethod(\"superOnAttach\")\n        } catch (e: NotFoundException) {\n            Assert.fail(\"找不到superOnAttach\")\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/InstrumentationTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport org.junit.Assert\nimport org.junit.Test\n\nclass InstrumentationTransformTest : AbstractTransformTest() {\n\n    @Test\n    fun testInstrumentationTransform() {\n        val targetClass = sLoader[\"test.TestInstrumentation\"]\n\n        val allInputClass = setOf(targetClass)\n\n        val applicationTransform = InstrumentationTransform()\n        applicationTransform.mClassPool = sLoader\n        applicationTransform.setup(allInputClass)\n\n        applicationTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                transform.transform(it)\n            }\n        }\n\n        allInputClass.forEach {\n            Assert.assertEquals(\n                \"Instrumentation父类应该都变为了ShadowInstrumentation\",\n                \"com.tencent.shadow.core.runtime.ShadowInstrumentation\",\n                it.classFile.superclass\n            )\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/PackageManagerTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform.ShadowTransform.Companion.SelfClassNamePlaceholder\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.CtClass\nimport javassist.CtMethod\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\n\nclass PackageManagerTransformTest : AbstractTransformTest() {\n\n    val packageManagerClazz = sLoader[\"android.content.pm.PackageManager\"]\n\n    @Test\n    fun testPackageManagerTransform() {\n        val allInputClass = setOf(\n            sLoader[\"test.TestPackageManager\"],\n            sLoader[\"test.TestPackageManager$1\"],\n            sLoader[\"test.TestPackageManager$1$1\"]\n        )\n\n        val packageManagerTransform = PackageManagerTransform()\n        packageManagerTransform.mClassPool = sLoader\n        packageManagerTransform.mClassPool.makeInterface(SelfClassNamePlaceholder)\n        packageManagerTransform.setup(allInputClass)\n\n        val methods = arrayOf(\"getApplicationInfo\", \"getActivityInfo\")\n\n\n        allInputClass.forEach {\n            //将测试类的包名改为Java的非法包名字符，测试Proguard混淆成这种形式的jar的场景\n            it.name = \"1.${it.simpleName}\"\n\n            for (method in methods) {\n                beforeTransformCheck(it, method)\n            }\n        }\n\n\n\n        packageManagerTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                it.defrost()\n                transform.transform(it)\n                it.writeFile(WRITE_FILE_DIR)\n            }\n        }\n\n\n\n        allInputClass.forEach {\n            for (method in methods) {\n                afterTransformCheck(it, method)\n            }\n        }\n\n\n    }\n\n\n    fun beforeTransformCheck(clazz: CtClass, method: String) {\n        val getApplicationMethods = packageManagerClazz.getDeclaredMethods(method)\n\n        assertTrue(\n            \"transform 前应该可以找到PackageManager的\" + method + \"的调用\",\n            findCall(getApplicationMethods, clazz)\n        )\n    }\n\n\n    fun afterTransformCheck(clazz: CtClass, method: String) {\n        val getManagerMethods = packageManagerClazz.getDeclaredMethods(method)\n\n        assertTrue(\n            \"transform 后应该可以不能找到PackageManager的\" + method + \"的调用\",\n            !findCall(getManagerMethods, clazz)\n        )\n\n\n        val methods2: List<CtMethod> =\n            getTargetMethods(sLoader, arrayOf(clazz.name), arrayOf(method + \"_shadow\"))\n\n        assertTrue(\n            method + \"_shadow方法应该能找到,且应该只有一个\",\n            methods2.size == 1\n        )\n\n        assertTrue(\n            method + \"_shadow方法调用也应该可以找到\",\n            findCall(arrayOf(clazz.getDeclaredMethod(method + \"_shadow\")), clazz)\n        )\n    }\n\n    fun findCall(target: Array<CtMethod>, clazz: CtClass): Boolean {\n        clazz.defrost()\n        var isFind = false\n        for (methods in target) {\n            if (matchMethodCallInClass(methods, clazz)) {\n                isFind = true\n                break\n            }\n        }\n        return isFind\n    }\n\n\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/ReceiverSupportTransformTest.kt",
    "content": "package com.tencent.shadow.core.transform.specific\n\nimport android.content.Context\nimport android.content.Intent\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.ClassPool\nimport javassist.Loader\nimport org.junit.Assert\nimport org.junit.Before\nimport org.junit.Test\n\nclass ReceiverSupportTransformTest : AbstractTransformTest() {\n\n    @Before\n    fun setUp() {\n        val classPool = ClassPool(null)\n        classPool.appendSystemPath()\n        val allInputClass = classPool[\n                arrayOf(\n                    \"test.AceReceiver\",\n                    \"test.BarReceiver\",\n                    \"test.CatReceiver\",\n                    \"test.DogReceiver\",\n                    \"test.EggReceiver\",\n                    \"test.EggReceiver\\$FoxReceiver\",\n                )\n        ].toMutableSet()\n\n        val applicationTransform = ReceiverSupportTransform()\n        applicationTransform.mClassPool = classPool\n        applicationTransform.setup(allInputClass)\n\n        applicationTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                transform.transform(it)\n                it.writeFile(WRITE_FILE_DIR)\n            }\n        }\n    }\n\n    private fun commonTestLogic(testClassName: String, expectLog: Array<String>) {\n        //加载修改后的类对象\n        val ctClass = dLoader[testClassName]\n        val loader = Loader(this.javaClass.classLoader, dLoader)\n        loader.delegateLoadingOf(\"android.content.\")\n        val clazz = try {\n            loader.loadClass(testClassName)\n        } catch (e: ClassNotFoundException) {\n            ctClass.classPool.toClass(\n                ctClass,\n                test.TestActivity::class.java,\n                loader,\n                test.TestActivity::class.java.protectionDomain\n            )\n        }\n\n        //构造实例，调用onReceive方法，检查log记录的字符串List是否符合预期\n        val constructor = clazz.getDeclaredConstructor(List::class.java)\n        constructor.trySetAccessible()\n        val onReceive =\n            clazz.getMethod(\"onReceive\", Context::class.java, Intent::class.java)\n        onReceive.trySetAccessible()\n        val log = clazz.getDeclaredField(\"log\")\n        log.trySetAccessible()\n        val receiver = constructor.newInstance(mutableListOf<String>())\n        val context = Context()\n        val intent = Intent()\n        onReceive.invoke(receiver, context, intent)\n        val logList: List<String> = log.get(receiver) as List<String>\n        Assert.assertArrayEquals(expectLog, logList.toTypedArray())\n    }\n\n    @Test\n    fun testAceReceiver() {\n        commonTestLogic(\n            \"test.AceReceiver\",\n            arrayOf(\n                \"AceReceiver onReceive\"\n            )\n        )\n    }\n\n    @Test\n    fun testBarReceiver() {\n        commonTestLogic(\n            \"test.BarReceiver\",\n            arrayOf(\n                \"BarReceiver onReceive\"\n            )\n        )\n    }\n\n    @Test\n    fun testCatReceiver() {\n        commonTestLogic(\n            \"test.CatReceiver\",\n            arrayOf(\n                \"CatReceiver onReceive enter\",\n                \"BarReceiver onReceive\",\n                \"CatReceiver onReceive leave\",\n            )\n        )\n    }\n\n    @Test\n    fun testDogReceiver() {\n        commonTestLogic(\n            \"test.DogReceiver\",\n            arrayOf(\n                \"CatReceiver onReceive enter\",\n                \"BarReceiver onReceive\",\n                \"CatReceiver onReceive leave\",\n            )\n        )\n    }\n\n    /**\n     * DogReceiver本来就没有override onReceive，我们也不应该给它添加。\n     */\n    @Test(expected = javassist.NotFoundException::class)\n    fun testDogReceiverDoNotHaveOnReceiveMethod() {\n        val ctClass = dLoader[\"test.DogReceiver\"]\n        ctClass.getDeclaredMethod(\"onReceive\")\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/ServiceTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport org.junit.Test\n\nclass ServiceTransformTest : SimpleRenameTransformTest(\n    ServiceTransform(), arrayOf(\"test.TestService\"),\n    \"getService\", \"com.tencent.shadow.core.runtime.ShadowService\",\n    mapOf(\"()Landroid/app/Service;\" to \"()Lcom/tencent/shadow/core/runtime/ShadowService;\")\n) {\n\n    @Test\n    fun testServiceTransform() {\n        doTest()\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/SimpleRenameTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.NotFoundException\nimport org.junit.Assert\n\nabstract class SimpleRenameTransformTest(\n    private val renameTransform: SimpleRenameTransform,\n    private val allInputClassName: Array<String>, private val methodName: String,\n    private val newSuperClassName: String, private val methodFromToMap: Map<String, String>\n) : AbstractTransformTest() {\n\n    protected fun doTest() {\n        renameTransform.mClassPool = sLoader\n        val allInputClass = sLoader[allInputClassName].toMutableSet()\n        renameTransform.setup(allInputClass)\n\n        renameTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                Assert.assertTrue(\n                    \"transform前应该能找到\" + methodName + \"方法\",\n                    try {\n                        it.getMethod(methodName, methodFromToMap.entries.first().key)\n                        true\n                    } catch (e: NotFoundException) {\n                        false\n                    }\n                )\n\n                transform.transform(it)\n            }\n        }\n\n        allInputClass.forEach {\n            Assert.assertEquals(\"父类应该都变为了新的父类\", it.classFile.superclass, newSuperClassName)\n\n            Assert.assertTrue(\n                \"原来的方法应该找不到了\",\n                try {\n                    it.getMethod(methodName, methodFromToMap.entries.first().key)\n                    false\n                } catch (e: NotFoundException) {\n                    true\n                }\n            )\n\n            Assert.assertTrue(\n                \"应该能找到签名变化了的方法\",\n                try {\n                    it.getMethod(methodName, methodFromToMap.entries.first().value)\n                    true\n                } catch (e: NotFoundException) {\n                    false\n                }\n            )\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform/src/test/kotlin/com/tencent/shadow/core/transform/specific/WebViewTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform.specific\n\nimport com.tencent.shadow.core.transform_kit.AbstractTransformTest\nimport javassist.CtClass\nimport org.junit.Assert\nimport org.junit.Test\n\n/**\n * ./gradlew -p projects/sdk/core :transform:test --tests com.tencent.shadow.core.transform.specific.WebViewTransformTest\n */\nclass WebViewTransformTest : AbstractTransformTest() {\n\n    val webViewClazz = sLoader[\"android.webkit.WebView\"]\n    val shadowWebViewClazz = sLoader[\"com.tencent.shadow.core.runtime.ShadowWebView\"]\n\n    @Test\n    fun testWebViewTransform() {\n        val allInputClass = setOf(\n            sLoader[\"test.TestWebView\"],\n            sLoader[\"com.tencent.shadow.core.runtime.ShadowWebView\"]\n        )\n\n        val webViewTransform = WebViewTransform()\n        webViewTransform.mClassPool = sLoader\n        webViewTransform.setup(allInputClass)\n\n        allInputClass.forEach {\n            beforeTransformCheck(it)\n        }\n\n        webViewTransform.list.forEach { transform ->\n            transform.filter(allInputClass).forEach {\n                transform.transform(it)\n            }\n        }\n\n        allInputClass.forEach {\n            afterTransformCheck(it)\n        }\n    }\n\n    private fun beforeTransformCheck(clazz: CtClass) {\n        if (clazz.classFile.name == \"test.TestWebView\") {\n            Assert.assertTrue(\n                webViewClazz.name + \" 构造器方法调用应该可以找到\",\n                matchConstructorCallInClass(webViewClazz.name, clazz)\n            )\n        }\n    }\n\n    private fun afterTransformCheck(clazz: CtClass) {\n        if (clazz.classFile.name == \"test.TestWebView\") {\n            Assert.assertEquals(\n                \"WebView父类应该都变为了ShadowWebView\",\n                \"com.tencent.shadow.core.runtime.ShadowWebView\",\n                clazz.classFile.superclass\n            )\n\n            Assert.assertTrue(\n                webViewClazz.name + \"构造器方法调用应该没有了\",\n                !matchConstructorCallInClass(webViewClazz.name, clazz)\n            )\n\n            Assert.assertTrue(\n                shadowWebViewClazz.name + \" 构造器方法调用应该可以找到\",\n                matchConstructorCallInClass(shadowWebViewClazz.name, clazz)\n            )\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/build.gradle",
    "content": "apply plugin: 'kotlin'\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    api \"com.android.tools.build:gradle:$build_gradle_version\"\n    api \"com.android.tools:common:$android_build_tools_version\"\n    api \"org.javassist:javassist:$javassist_version\"\n    api gradleApi()\n    testImplementation \"junit:junit:$junit_version\"\n    testImplementation \"commons-io:commons-io:$commons_io_jvm_version\"\n}\n\ncompileKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n\ncompileTestKotlin {\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n    }\n}\n\njava {\n    sourceCompatibility JavaVersion.VERSION_11\n    targetCompatibility JavaVersion.VERSION_11\n}\n\ntask testJar(type: Jar, dependsOn: testClasses) {\n    baseName = \"test-${project.archivesBaseName}\"\n    from sourceSets.test.output\n}\n\nconfigurations {\n    tests\n}\n\nartifacts {\n    tests testJar\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/java/javassist/EnhancedCodeConverter.java",
    "content": "package javassist;\n\nimport javassist.convert.TransformCallExceptSuperCallToStatic;\nimport javassist.convert.TransformNewClassFix;\n\npublic class EnhancedCodeConverter extends CodeConverter {\n\n    public void redirectMethodCallExceptSuperCallToStatic(CtMethod origMethod, CtMethod substMethod) throws CannotCompileException {\n        transformers = new TransformCallExceptSuperCallToStatic(transformers, origMethod,\n                substMethod);\n    }\n\n    public void replaceNew(CtClass oldClass, CtClass newClass) {\n        transformers = new TransformNewClassFix(transformers, oldClass.getName(),\n                newClass.getName());\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/java/javassist/convert/TransformCallExceptSuperCallToStatic.java",
    "content": "package javassist.convert;\n\nimport javassist.ClassPool;\nimport javassist.CtClass;\nimport javassist.CtMethod;\nimport javassist.NotFoundException;\nimport javassist.bytecode.BadBytecode;\nimport javassist.bytecode.CodeIterator;\nimport javassist.bytecode.ConstPool;\n\npublic class TransformCallExceptSuperCallToStatic extends TransformCallToStatic {\n    public TransformCallExceptSuperCallToStatic(Transformer next, CtMethod origMethod, CtMethod substMethod) {\n        super(next, origMethod, substMethod);\n    }\n\n    // COPY FROM TransformCall\n    @Override\n    public int transform(CtClass clazz, int pos, CodeIterator iterator, ConstPool cp) throws BadBytecode {\n        int c = iterator.byteAt(pos);\n        if (c == INVOKEINTERFACE || c == INVOKESTATIC || c == INVOKEVIRTUAL) { // THE ONLY DIFFERENCE WITH TransformCall\n            int index = iterator.u16bitAt(pos + 1);\n            String cname = cp.eqMember(methodname, methodDescriptor, index);\n            if (cname != null && matchClass(cname, clazz.getClassPool())) {\n                int ntinfo = cp.getMemberNameAndType(index);\n                pos = match(c, pos, iterator,\n                        cp.getNameAndTypeDescriptor(ntinfo), cp);\n            }\n        }\n\n        return pos;\n    }\n\n    // COPY FROM TransformCall\n    private boolean matchClass(String name, ClassPool pool) {\n        if (classname.equals(name))\n            return true;\n\n        try {\n            CtClass clazz = pool.get(name);\n            CtClass declClazz = pool.get(classname);\n            if (clazz.subtypeOf(declClazz))\n                try {\n                    CtMethod m = clazz.getMethod(methodname, methodDescriptor);\n                    return m.getDeclaringClass().getName().equals(classname);\n                } catch (NotFoundException e) {\n                    // maybe the original method has been removed.\n                    return true;\n                }\n        } catch (NotFoundException e) {\n            return false;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/java/javassist/convert/TransformNewClassFix.java",
    "content": "package javassist.convert;\n\nimport javassist.CannotCompileException;\nimport javassist.CtClass;\nimport javassist.bytecode.CodeAttribute;\nimport javassist.bytecode.CodeIterator;\nimport javassist.bytecode.ConstPool;\n\n/**\n * 完全复制自TransformNewClass代码\n * 去掉“\"NEW followed by no DUP was found\"检查\n */\nfinal public class TransformNewClassFix extends Transformer {\n    private int nested;\n    private String classname, newClassName;\n    private int newClassIndex, newMethodNTIndex, newMethodIndex;\n\n    public TransformNewClassFix(Transformer next,\n                                String classname, String newClassName) {\n        super(next);\n        this.classname = classname;\n        this.newClassName = newClassName;\n    }\n\n    @Override\n    public void initialize(ConstPool cp, CodeAttribute attr) {\n        nested = 0;\n        newClassIndex = newMethodNTIndex = newMethodIndex = 0;\n    }\n\n    /**\n     * Modifies a sequence of\n     * NEW classname\n     * DUP\n     * ...\n     * INVOKESPECIAL classname:method\n     */\n    @Override\n    public int transform(CtClass clazz, int pos, CodeIterator iterator,\n                         ConstPool cp) throws CannotCompileException {\n        int index;\n        int c = iterator.byteAt(pos);\n        if (c == NEW) {\n            index = iterator.u16bitAt(pos + 1);\n            if (cp.getClassInfo(index).equals(classname)) {\n\n                if (newClassIndex == 0)\n                    newClassIndex = cp.addClassInfo(newClassName);\n\n                iterator.write16bit(newClassIndex, pos + 1);\n                ++nested;\n            }\n        } else if (c == INVOKESPECIAL) {\n            index = iterator.u16bitAt(pos + 1);\n            int typedesc = cp.isConstructor(classname, index);\n            if (typedesc != 0 && nested > 0) {\n                int nt = cp.getMethodrefNameAndType(index);\n                if (newMethodNTIndex != nt) {\n                    newMethodNTIndex = nt;\n                    newMethodIndex = cp.addMethodrefInfo(newClassIndex, nt);\n                }\n\n                iterator.write16bit(newMethodIndex, pos + 1);\n                --nested;\n            }\n        }\n\n        return pos;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\nimport org.gradle.api.Project\nimport java.io.BufferedWriter\nimport java.io.DataOutputStream\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.FileWriter\nimport java.io.OutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipOutputStream\nimport kotlin.system.measureTimeMillis\n\nabstract class AbstractTransform(\n    project: Project,\n    classPoolBuilder: ClassPoolBuilder\n) : JavassistTransform(project, classPoolBuilder) {\n\n    protected abstract val mTransformManager: AbstractTransformManager\n    private val mOverrideCheck = OverrideCheck()\n    private lateinit var mDebugClassJar: File\n    private lateinit var mDebugClassJarZOS: ZipOutputStream\n\n\n    private fun cleanDebugClassFileDir() {\n        val transformTempDir = File(project.buildDir, \"transform-temp\")\n        transformTempDir.deleteRecursively()\n        transformTempDir.mkdirs()\n        mDebugClassJar = File.createTempFile(\"transform-temp\", \".jar\", transformTempDir)\n        mDebugClassJarZOS = ZipOutputStream(FileOutputStream(mDebugClassJar))\n    }\n\n    override fun beforeTransform() {\n        super.beforeTransform()\n        ReplaceClassName.resetErrorCount()\n        cleanDebugClassFileDir()\n    }\n\n    override fun onTransform() {\n        //Fixme: 这里的OverrideCheck.prepare会对mCtClassInputMap产生影响\n        //原本预期是不会产生任何影响的。造成了ApplicationInfoTest失败，测试Activity没有被修改superclass。\n//        mOverrideCheck.prepare(mCtClassInputMap.keys.toSet())\n\n        mTransformManager.setupAll(allInputCtClass)\n        mTransformManager.fireAll(allInputCtClass)\n    }\n\n    override fun afterTransform() {\n        super.afterTransform()\n\n        mDebugClassJarZOS.flush()\n        mDebugClassJarZOS.close()\n\n        //CtClass在编辑后，其对象中的各种信息，比如superClass并没有更新。\n        //所以需要重新创建一个ClassPool，加载转换后的类，用于各种转换后的检查。\n        val debugClassPool = classPoolBuilder.build()\n        java.util.jar.JarFile(mDebugClassJar).use { jarFile ->\n            jarFile.entries().iterator().forEach { entry ->\n                if (!entry.name.endsWith(\".class\")) {\n                    return@forEach\n                }\n                jarFile.getInputStream(entry).use {\n                    // 直接从流创建 CtClass\n                    debugClassPool.makeClass(it)\n                }\n            }\n        }\n        val inputClassNames = allInputCtClass.map { it.name }\n        onCheckTransformedClasses(debugClassPool, inputClassNames)\n    }\n\n    override fun onOutputClass(className: String, outputStream: OutputStream) {\n        classPool[className].debugWriteJar(mDebugClassJarZOS)\n        super.onOutputClass(className, outputStream)\n    }\n\n    private fun CtClass.debugWriteJar(outputStream: ZipOutputStream) {\n        try {\n            val entryName = name.replace('.', '/') + \".class\"\n            outputStream.putNextEntry(ZipEntry(entryName))\n            val p = stopPruning(true)\n            toBytecode(DataOutputStream(outputStream))\n            defrost()\n            stopPruning(p)\n        } catch (e: Exception) {\n            outputStream.close()\n            throw RuntimeException(e)\n        }\n    }\n\n    open fun onCheckTransformedClasses(debugClassPool: ClassPool, classNames: List<String>) {\n        var delayException: Exception? = null\n        val start1 = System.currentTimeMillis()\n        try {\n            checkReplacedClassHaveRightMethods(debugClassPool, classNames)\n        } catch (e: Exception) {\n            if (delayException == null) {\n                delayException = e\n            } else {\n                delayException.addSuppressed(e)\n            }\n        }\n        project.logger.info(\"checkReplacedClassHaveRightMethods完毕，耗时(ms):${System.currentTimeMillis() - start1}\")\n\n        val start2 = System.currentTimeMillis()\n        try {\n            val t2 = measureTimeMillis {\n                //                checkOverrideMethods(debugClassPool, classNames)\n            }\n            System.err.println(\"t2:$t2\")\n        } catch (e: Exception) {\n            if (delayException == null) {\n                delayException = e\n            } else {\n                delayException.addSuppressed(e)\n            }\n        }\n        project.logger.info(\"checkOverrideMethods完毕，耗时(ms):${System.currentTimeMillis() - start2}\")\n\n        if (delayException != null) {\n            throw delayException\n        }\n    }\n\n    /**\n     * 检查转换后的类，其中被替换了的类有实现被调用的方法\n     */\n    private fun checkReplacedClassHaveRightMethods(\n        debugClassPool: ClassPool,\n        classNames: List<String>\n    ) {\n        val result = ReplaceClassName.checkAll(debugClassPool, classNames)\n        if (result.isNotEmpty()) {\n            val tempFile = File.createTempFile(\n                \"shadow_replace_class_have_right_methods\",\n                \".txt\",\n                project.buildDir\n            )\n            val bw = BufferedWriter(FileWriter(tempFile))\n\n            result.forEach {\n                val defClass = it.key\n                bw.appendln(\"Class ${defClass}中缺少方法:\")\n                val methodMap = it.value\n                methodMap.forEach {\n                    val methodString = it.key\n                    val useClass = it.value\n\n                    bw.appendln(\"${methodString}被这些类调用了:\")\n                    useClass.forEach {\n                        bw.appendln(it)\n                    }\n                }\n                bw.newLine()\n            }\n            bw.flush()\n            bw.close()\n            throw IllegalStateException(\"存在转换后被调用方法未实现的问题，详见${tempFile.absolutePath}\")\n        }\n    }\n\n    private fun checkOverrideMethods(debugClassPool: ClassPool, classNames: List<String>) {\n        val result = mOverrideCheck.check(debugClassPool, classNames)\n        if (result.isNotEmpty()) {\n            val tempFile = File.createTempFile(\"shadow_override_check\", \".txt\", project.buildDir)\n            val bw = BufferedWriter(FileWriter(tempFile))\n            result.forEach {\n                bw.appendln(\"In Class ${it.key} 这些方法不再Override父类了:\")\n                it.value.map { \"${it.first.name}:${it.first.signature}(转换前定义在${it.second})\" }\n                    .forEach {\n                        bw.appendln(it)\n                    }\n                bw.newLine()\n            }\n            bw.flush()\n            bw.close()\n            throw IllegalStateException(\"存在Override方法转换后不再Override的情况，详见${tempFile.absolutePath}\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformManager.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\n\nabstract class AbstractTransformManager(private val classPool: ClassPool) {\n    abstract val mTransformList: List<SpecificTransform>\n\n    fun setupAll(allInputCtClass: Set<CtClass>) {\n        mTransformList.forEach {\n            it.mClassPool = classPool\n            it.setup(allInputCtClass)\n        }\n    }\n\n    fun fireAll(allInputCtClass: Set<CtClass>) {\n        mTransformList.flatMap { it.list }.forEach { transform ->\n            transform.filter(allInputCtClass).forEach {\n                transform.transform(it)\n            }\n        }\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AndroidClassPoolBuilder.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.LoaderClassPath\nimport org.gradle.api.Project\nimport org.gradle.internal.classloader.VisitableURLClassLoader\nimport java.io.File\n\nclass AndroidClassPoolBuilder(\n    project: Project,\n    val contextClassLoader: ClassLoader,\n    val androidJar: File\n) : ClassPoolBuilder {\n    private val logger = project.logger\n\n    override fun build(): ClassPool {\n        //这里使用useDefaultPath:false是因为这里取到的contextClassLoader不包含classpath指定进来的runtime\n        //所以在外部先获取一个包含了runtime的contextClassLoader传进来\n        val classPool = AutoMakeMissingClassPool(false)\n\n        classPool.appendClassPath(LoaderClassPath(contextClassLoader))\n        if (logger.isInfoEnabled && contextClassLoader is VisitableURLClassLoader) {\n            val sb = StringBuilder()\n            sb.appendln()\n            for (urL in contextClassLoader.urLs) {\n                sb.appendln(urL)\n            }\n            logger.info(\"AndroidClassPoolBuilder appendClassPath contextClassLoader URLs:$sb\")\n        }\n\n        classPool.appendClassPath(androidJar.absolutePath)\n        if (logger.isInfoEnabled) {\n            logger.info(\"AndroidClassPoolBuilder appendClassPath androidJar:${androidJar.absolutePath}\")\n        }\n\n        return classPool\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/AutoMakeMissingClassPool.kt",
    "content": "package com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\n\nclass AutoMakeMissingClassPool(useDefaultPath: Boolean) : ClassPool(useDefaultPath) {\n\n    companion object {\n        fun isFromFixTypes2Called(newThrowable: Throwable): Boolean {\n            for (stackTraceElement in newThrowable.stackTrace) {\n                if (stackTraceElement.methodName == \"fixTypes2\") {\n                    return true\n                }\n            }\n            return false\n        }\n    }\n\n    override fun get0(classname: String?, useCache: Boolean): CtClass? {\n        var get0 = super.get0(classname, useCache)\n\n        // 来自javassist.bytecode.stackmap.TypeData.TypeVar.fixTypes2的调用时，\n        // 如果类不存在，就构造一个。\n        // fixTypes2是重建StackMap的步骤，参考：https://stackoverflow.com/a/37310409/11616914\n        // 我们的Transform不会去修改找不到的类型相关的代码，\n        // 而fixTypes2处理的逻辑是在确定泛型的下界，\n        // 由于我们没改任何跟找不到类型相关的逻辑，所以未知类型的父类重定义为Object，应该没有危险。\n        //\n        // 这里必须判断是来自的fixTypes2的调用，而不是所有调用都构造类型，是因为存在像\n        // javassist.compiler.MemberResolver.lookupClass0\n        // 依赖NotFoundException的逻辑存在。\n        if (get0 == null && isFromFixTypes2Called(Throwable())) {\n            get0 = makeClass(classname)\n            if (useCache) cacheCtClass(get0.getName(), get0, false)\n        }\n\n        return get0\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ClassTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport com.android.SdkConstants\nimport com.android.utils.FileUtils\nimport org.gradle.api.Project\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.OutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipInputStream\n\n/**\n * 类转换基类\n *\n * @author cubershi\n */\nabstract class ClassTransform(val project: Project) {\n\n    /**\n     * 获取输入文件对应的输出文件路径.即将文件this路径中的inputDir部分替换为outputDir.\n     */\n    private fun File.toOutputFile(inputDir: File, outputDir: File): File {\n        return File(outputDir, this.toRelativeString(inputDir))\n    }\n\n    fun input(inputs: Iterable<TransformInput>) {\n        val logger = project.logger\n        if (logger.isInfoEnabled) {\n            val sb = StringBuilder()\n            sb.appendln()\n            inputs.forEach {\n                sb.appendln(it.asFile().absolutePath)\n            }\n            logger.info(\"ClassTransform input paths:$sb\")\n        }\n\n        inputs.forEach { transformInput ->\n            when (transformInput.kind) {\n                TransformInput.Kind.DIRECTORY -> {\n                    val inputDir = transformInput.asFile()\n                    val allFiles = FileUtils.getAllFiles(inputDir)\n                    allFiles.filter {\n                        it?.name?.endsWith(SdkConstants.DOT_CLASS) ?: false\n                    }.forEach {\n                        val className = loadDotClassFile(it)\n                        transformInput.inputClassNames.add(className)\n                    }\n                }\n\n                TransformInput.Kind.JAR -> {\n                    ZipInputStream(FileInputStream(transformInput.asFile())).use { zis ->\n                        var entry: ZipEntry?\n                        while (true) {\n                            entry = zis.nextEntry\n                            if (entry == null) break\n\n                            val entryName = entry.name\n\n                            // 忽略一些实际上不会进入编译classpath的文件\n                            if (entry.isDirectory) continue\n                            if (!entryName.endsWith(SdkConstants.DOT_CLASS)) continue\n                            if (entryName.startsWith(\"META-INF/\", true)) continue\n                            if (entryName.endsWith(\"module-info.class\", true)) continue\n                            if (entryName.endsWith(\"package-info.class\", true)) continue\n\n                            val className = loadClassFromJar(zis)\n                            transformInput.inputClassNames.add(className)\n                        }\n                    }\n                }\n            }\n        }\n\n\n    }\n\n    abstract fun onOutputClass(className: String, outputStream: OutputStream)\n\n    /**\n     * 让子类实现的字节码编辑框架加载.class文件，加载后返回类名\n     */\n    abstract fun loadDotClassFile(classFile: File): String\n\n    /**\n     * 让子类实现的字节码编辑框架加载jar中的class，加载后返回类名\n     */\n    abstract fun loadClassFromJar(zipInputStream: ZipInputStream): String\n\n    abstract fun onTransform()\n\n    /**\n     * 每一次执行transform前调用。在一次构建中可能有多个Variant，多个Variant会共用同一个\n     * Transform对象（就是这个类的对象）。在这里提供一个时机清理transform过程中产生的缓存，\n     * 避免对下一次transform产生影响。\n     */\n    open fun beforeTransform() {\n    }\n\n    open fun afterTransform() {\n    }\n\n}\n\n/**\n * 输入数据的封装\n * 外部Transform框架的适配器DeprecatedTransformWrapper或者GradleTransformWrapper\n * 把它们的输入封装成这个抽象类的子类\n */\nabstract class TransformInput {\n    /**\n     * 一个TransformInput可能是Dir包含多个class文件，也可能是一个jar包包含多个class文件。\n     * 这里把它们记下来，等输出的时候要按这个名单输出\n     */\n    val inputClassNames = mutableSetOf<String>()\n\n    enum class Kind { DIRECTORY, JAR }\n\n    abstract val kind: Kind\n    abstract fun asFile(): File\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/JavassistTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\nimport org.gradle.api.Project\nimport java.io.File\nimport java.io.OutputStream\nimport java.util.zip.ZipInputStream\n\nopen class JavassistTransform(project: Project, val classPoolBuilder: ClassPoolBuilder) :\n    ClassTransform(project) {\n    /**\n     * 所有classPool.makeClass生成的CtClass存起来，\n     * 等AbstractTransformManager处理类时直接从这里\n     * 拿到所有CtClass作为字节码编辑的输入数据。\n     */\n    val allInputCtClass = mutableSetOf<CtClass>()\n    lateinit var classPool: ClassPool\n\n    override fun onOutputClass(className: String, outputStream: OutputStream) {\n        classPool[className].writeOut(outputStream)\n    }\n\n    override fun loadDotClassFile(classFile: File): String {\n        val ctClass: CtClass = classFile.inputStream().use {\n            classPool.makeClass(it)\n        }\n        allInputCtClass.add(ctClass)\n        return ctClass.name\n    }\n\n    override fun loadClassFromJar(zipInputStream: ZipInputStream): String {\n        val ctClass = classPool.makeClass(zipInputStream)\n        allInputCtClass.add(ctClass)\n        return ctClass.name\n    }\n\n    override fun beforeTransform() {\n        super.beforeTransform()\n        allInputCtClass.clear()\n        classPool = classPoolBuilder.build()\n    }\n\n\n    override fun onTransform() {\n        //do nothing.\n    }\n\n    fun CtClass.writeOut(output: OutputStream) {\n        this.toBytecode(java.io.DataOutputStream(output))\n    }\n\n}\n\ninterface ClassPoolBuilder {\n    fun build(): ClassPool\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/OverrideCheck.kt",
    "content": "package com.tencent.shadow.core.transform_kit\n\nimport com.android.build.gradle.internal.utils.toImmutableMap\nimport javassist.ClassPool\nimport javassist.CtClass\nimport javassist.CtMethod\nimport javassist.NotFoundException\n\ntypealias Method_OriginalDeclaringClass = Pair<CtMethod, String>\n\nclass OverrideCheck {\n    /**\n     * key:Transform前的类名\n     * value：Override的方法\n     */\n    private val methodMap: MutableMap<String, Collection<Method_OriginalDeclaringClass>> =\n        hashMapOf()\n\n    /**\n     * 这个map用于应对Transform后类名变化的情况\n     * Transform后，check时，从CtClass取出新类名\n     *\n     * key:Transform前的类名\n     * value: CtClass\n     */\n    private val ctClassMap: MutableMap<String, CtClass> = hashMapOf()\n\n    private fun String.isKotlinClass(): Boolean {\n        return startsWith(\"kotlin\")\n    }\n\n    fun prepare(inputClasses: Set<CtClass>) {\n        methodMap.clear()\n        ctClassMap.clear()\n\n        inputClasses\n            .filter { it.packageName != null }\n            .filter {\n                //kotlinx里有一些方法的覆盖检查不出来，反正我们也不改它，就不检查了。\n                it.packageName.isKotlinClass().not()\n            }.filter {\n                try {\n                    it.methods\n                    it.superclass\n                    true\n                } catch (e: NotFoundException) {\n                    false\n                }\n            }.forEach { clazz ->\n                val name = clazz.name\n                try {\n                    methodMap[name] = clazz.findAllOverrideMethods()\n                    ctClassMap[name] = clazz\n                } catch (e: Exception) {\n                    throw RuntimeException(\"处理${name}时发生错误\", e)\n                }\n            }\n    }\n\n    fun makeNewNameToOldNameMap(): HashMap<String, String> {\n        val newNameToOldName = hashMapOf<String, String>()\n        ctClassMap.forEach {\n            newNameToOldName[it.value.name] = it.key\n        }\n        return newNameToOldName\n    }\n\n    fun getOverrideMethods() = methodMap.toImmutableMap()\n\n    fun check(\n        debugClassPool: ClassPool,\n        classNames: List<String>\n    ): Map<String, List<Method_OriginalDeclaringClass>> {\n        val newNameToOldName = makeNewNameToOldNameMap()\n\n        val errorResult: HashMap<String, MutableList<Method_OriginalDeclaringClass>> = hashMapOf()\n        classNames\n            .filter { it.contains(\".\") }\n            .filter {\n                it.isKotlinClass().not()\n            }.filter {\n                val clazz = debugClassPool[it]!!\n                try {\n                    clazz.methods\n                    clazz.superclass\n                    true\n                } catch (e: NotFoundException) {\n                    false\n                }\n            }.forEach { className ->\n                try {\n                    val oldName = newNameToOldName[className]!!\n                    val methods = methodMap[oldName]!!\n                    val clazz = debugClassPool[className]!!\n                    methods.forEach {\n                        val isOverride = clazz.isMethodOverride(it.first)\n                        if (!isOverride) {\n                            var list = errorResult[className]\n                            if (list == null) {\n                                list = mutableListOf()\n                                errorResult[className] = list\n                            }\n                            list.add(it)\n                        }\n                    }\n                } catch (e: RuntimeException) {\n                    throw RuntimeException(\"className==$className\", e)\n                }\n\n            }\n        return errorResult\n    }\n\n    companion object {\n        private fun CtClass.findAllOverrideMethods(): List<Pair<CtMethod, String>> {\n            val methodsDeclaredInClass = mutableListOf<Pair<CtMethod, String>>()\n            declaredMethods.forEach {\n                try {\n                    val methodOfSuperClass = superclass.getMethod(it.name, it.signature)\n                    methodsDeclaredInClass.add(it to methodOfSuperClass.declaringClass.name)\n                } catch (ignored: NotFoundException) {\n                    try {\n                        val methodOfSuperClass = superclass.getMethod(it.name, it.genericSignature)\n                        methodsDeclaredInClass.add(it to methodOfSuperClass.declaringClass.name)\n                    } catch (ignored: NotFoundException) {\n                    }\n                }\n            }\n            return methodsDeclaredInClass\n        }\n\n        private fun CtClass.hasSameMethod(m: CtMethod) =\n            try {\n                try {\n                    getMethod(m.name, m.signature)\n                    true\n                } catch (ignored: NotFoundException) {\n                    getMethod(m.name, m.genericSignature)\n                    true\n                }\n            } catch (ignored: NotFoundException) {\n                false\n            }\n\n        private fun CtClass.isMethodOverride(originMethod: CtMethod) =\n            superclass.hasSameMethod(originMethod)\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/ReplaceClassName.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\nimport javassist.NotFoundException\nimport javassist.expr.ExprEditor\nimport javassist.expr.MethodCall\n\nobject ReplaceClassName {\n    private val oldToNewRenameMap = mutableMapOf<String, String>()\n\n    /**\n     * MutableMap<defClass, MutableMap<method, MutableSet<useClass>>>\n     */\n    private val errorResult: MutableMap<String, MutableMap<String, MutableSet<String>>> =\n        mutableMapOf()\n\n    fun resetErrorCount() {\n        oldToNewRenameMap.clear()\n        errorResult.clear()\n    }\n\n    fun replaceClassName(ctClass: CtClass, oldName: String, newName: String) {\n        ctClass.replaceClassName(oldName, newName)\n        oldToNewRenameMap[oldName] = newName\n    }\n\n    fun checkAll(\n        classPool: ClassPool,\n        inputClassNames: List<String>\n    ): Map<String, Map<String, Set<String>>> {\n        inputClassNames.forEach { inputClassName ->\n            val inputClass = classPool[inputClassName]\n            val oldNames = oldToNewRenameMap.keys\n            val newNames = oldToNewRenameMap.values\n            if (inputClass.refClasses.any { newNames.contains(it) }) {\n                oldNames.forEach { oldName ->\n                    val newName = oldToNewRenameMap[oldName]\n                    inputClass.checkMethodExist(classPool[oldName], classPool[newName])\n                }\n            }\n        }\n        return errorResult\n    }\n\n    /**\n     * 检查ctClass对refClassName引用的方法确实都存在\n     */\n    private fun CtClass.checkMethodExist(oldClass: CtClass, newClass: CtClass) {\n        val invokeClass = name\n        val refClassName = newClass.name\n        instrument(object : ExprEditor() {\n            override fun edit(m: MethodCall) {\n                if (m.className == refClassName) {\n                    try {\n                        oldClass.getMethod(m.methodName, m.signature)\n                    } catch (ignored: NotFoundException) {\n                        //替换前旧的类就没有这个方法，就不用管替换后的类是否实现了。\n                        return\n                    }\n\n                    try {\n                        newClass.getMethod(m.methodName, m.signature)\n                    } catch (ignored: NotFoundException) {\n                        val methodString = \"${m.methodName}:${m.signature}\"\n                        var methodMap = errorResult[refClassName]\n                        if (methodMap == null) {\n                            methodMap = mutableMapOf()\n                            errorResult[refClassName] = methodMap\n                        }\n                        var useSet = methodMap[methodString]\n                        if (useSet == null) {\n                            useSet = mutableSetOf()\n                            methodMap[methodString] = useSet\n                        }\n                        useSet.add(invokeClass)\n                    }\n                }\n            }\n        })\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/SpecificTransform.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\nimport javassist.CtMethod\nimport javassist.NotFoundException\nimport javassist.bytecode.CodeAttribute\nimport javassist.bytecode.MethodInfo\nimport javassist.bytecode.Opcode\n\nabstract class SpecificTransform {\n    private val _list = mutableListOf<TransformStep>()\n\n    val list: List<TransformStep> = _list\n\n    lateinit var mClassPool: ClassPool\n\n    fun newStep(transform: TransformStep) {\n        _list.add(transform)\n    }\n\n    abstract fun setup(allInputClass: Set<CtClass>)\n\n    fun CtMethod.copyDescriptorFrom(other: CtMethod) {\n        methodInfo.descriptor = other.methodInfo.descriptor\n    }\n\n    fun CtClass.isClassOf(className: String): Boolean {\n        var tmp: CtClass? = this\n        do {\n            if (tmp?.name == className) {\n                return true\n            }\n            try {\n                tmp = tmp?.superclass\n            } catch (e: NotFoundException) {\n                return false\n            }\n        } while (tmp != null)\n        return false\n    }\n\n    /**\n     * 查找目标class是否存在目标method的调用\n     */\n    fun matchMethodCallInClass(ctMethod: CtMethod, clazz: CtClass): Boolean {\n        for (methodInfo in clazz.classFile2.methods) {\n            methodInfo as MethodInfo\n            val codeAttr: CodeAttribute? = methodInfo.codeAttribute\n            val constPool = methodInfo.constPool\n            if (codeAttr != null) {\n                val iterator = codeAttr.iterator()\n                while (iterator.hasNext()) {\n                    val pos = iterator.next()\n                    val c = iterator.byteAt(pos)\n                    if (c == Opcode.INVOKEINTERFACE || c == Opcode.INVOKESPECIAL\n                        || c == Opcode.INVOKESTATIC || c == Opcode.INVOKEVIRTUAL\n                    ) {\n                        val index = iterator.u16bitAt(pos + 1)\n                        val cname = constPool.eqMember(\n                            ctMethod.name,\n                            ctMethod.methodInfo2.descriptor,\n                            index\n                        )\n                        val className = ctMethod.declaringClass.name\n                        val matched = cname != null && matchClass(\n                            ctMethod.name,\n                            ctMethod.methodInfo.descriptor,\n                            className,\n                            cname,\n                            clazz.classPool\n                        )\n                        if (matched) {\n                            return true\n                        }\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    private fun matchClass(\n        methodName: String,\n        methodDescriptor: String,\n        classname: String,\n        name: String,\n        pool: ClassPool\n    ): Boolean {\n        if (classname == name)\n            return true\n\n        try {\n            val clazz = pool.get(name)\n            val declClazz = pool.get(classname)\n            if (clazz.subtypeOf(declClazz))\n                try {\n                    val m = clazz.getMethod(methodName, methodDescriptor)\n                    return m.declaringClass.name == classname\n                } catch (e: NotFoundException) {\n                    // maybe the original method has been removed.\n                    return true\n                }\n\n        } catch (e: NotFoundException) {\n            return false\n        }\n\n        return false\n    }\n\n    companion object {\n        /**\n         * 过滤引用了某些类型的类\n         */\n        fun filterRefClasses(allAppClass: Set<CtClass>, targetClassList: List<String>) =\n            allAppClass.filter { ctClass ->\n                targetClassList.any { targetClass ->\n                    ctClass.refClasses.contains(targetClass)\n                }\n            }.toSet()\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/main/kotlin/com/tencent/shadow/core/transform_kit/TransformStep.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.CtClass\n\ninterface TransformStep {\n    fun filter(allInputClass: Set<CtClass>): Set<CtClass>\n\n    fun transform(ctClass: CtClass)\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/java/test/MethodRedirectToStatic.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage test;\n\npublic class MethodRedirectToStatic {\n\n    public static void main(String[] args) {\n        System.out.println(new MethodRedirectToStatic().test());\n    }\n\n    int add(int a, int b) {\n        return a + b;\n    }\n\n    public int test() {\n        return add(1, 2);\n    }\n}\n\nclass MethodRedirectToStatic2 {\n    public static int add2(MethodRedirectToStatic target, int a, int b) {\n        return target.add(a * 10, b * 10);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/java/test/override/Foo.java",
    "content": "package test.override;\n\nclass ArgBase {\n\n}\n\nclass Arg extends ArgBase {\n\n}\n\nclass NewArg extends ArgBase {\n\n}\n\nclass SuperSuper {\n    protected void ss1() {\n\n    }\n\n    final protected void methodCannotOverride() {\n\n    }\n}\n\nclass Super extends SuperSuper {\n    protected void s1(Arg arg) {\n\n    }\n\n    protected void s2(Arg arg) {\n\n    }\n}\n\nclass NewSuper extends SuperSuper {\n    protected void s1(NewArg newArg) {\n\n    }\n}\n\nclass Foo extends Super {\n\n    @Override\n    protected void ss1() {\n        super.ss1();\n    }\n\n    @Override\n    protected void s1(Arg arg) {\n        super.s1(arg);\n    }\n\n    @Override\n    protected void s2(Arg arg) {\n        super.s2(arg);\n    }\n}\n\nclass Bar extends Foo {\n    @Override\n    protected void ss1() {\n        super.ss1();\n    }\n\n    @Override\n    protected void s1(Arg arg) {\n        super.s1(arg);\n    }\n\n    @Override\n    protected void s2(Arg arg) {\n        super.s2(arg);\n    }\n}\n\n/**\n * 这个类通过Foo类型调用其父类SuperSuper类型的方法，\n * 所以这个类本身并没有引用SuperSuper类型。\n */\nclass UseFooAsSuperSuper {\n    void useFooAsSuperSuper() {\n        Foo foo = new Foo();\n        foo.methodCannotOverride();\n    }\n}\n"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/kotlin/com/tencent/shadow/core/transform_kit/AbstractTransformTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.*\nimport javassist.bytecode.CodeAttribute\nimport javassist.bytecode.MethodInfo\nimport javassist.bytecode.Opcode\nimport java.util.*\n\n\nabstract class AbstractTransformTest {\n    companion object {\n        const val WRITE_FILE_DIR = \"build/test_write_file\"\n    }\n\n    protected val sLoader: ClassPool = ClassPool.getDefault()\n    protected val dLoader: ClassPool = ClassPool(null)\n    protected val cLoader: Loader\n\n    init {\n        dLoader.appendSystemPath()\n        dLoader.insertClassPath(WRITE_FILE_DIR)\n        cLoader = Loader(dLoader)\n    }\n\n    protected fun make(name: String): Any {\n        return cLoader.loadClass(name).getConstructor().newInstance()\n    }\n\n    protected operator fun invoke(target: Any, method: String): Int {\n        val m = target.javaClass.getMethod(method, *arrayOfNulls(0))\n        val res = m.invoke(target, *arrayOfNulls(0))\n        return (res as Int).toInt()\n    }\n\n    /**\n     * 查找目标class对象的目标method\n     */\n    fun getTargetMethods(\n        classPool: ClassPool,\n        targetClassNames: Array<String>,\n        targetMethodName: Array<String>\n    ): List<CtMethod> {\n        val method_targets = ArrayList<CtMethod>()\n        for (targetClassName in targetClassNames) {\n            val methods = classPool[targetClassName].methods\n            method_targets.addAll(methods.filter { targetMethodName.contains(it.name) })\n        }\n        return method_targets\n    }\n\n    /**\n     * 查找目标class是否存在目标method的调用\n     */\n    fun matchMethodCallInClass(ctMethod: CtMethod, clazz: CtClass): Boolean {\n        for (methodInfo in clazz.classFile2.methods) {\n            methodInfo as MethodInfo\n            val codeAttr: CodeAttribute? = methodInfo.codeAttribute\n            val constPool = methodInfo.constPool\n            if (codeAttr != null) {\n                val iterator = codeAttr.iterator()\n                while (iterator.hasNext()) {\n                    val pos = iterator.next()\n                    val c = iterator.byteAt(pos)\n                    if (c == Opcode.INVOKEINTERFACE || c == Opcode.INVOKESPECIAL\n                        || c == Opcode.INVOKESTATIC || c == Opcode.INVOKEVIRTUAL\n                    ) {\n                        val index = iterator.u16bitAt(pos + 1)\n                        val cname = constPool.eqMember(\n                            ctMethod.name,\n                            ctMethod.methodInfo2.descriptor,\n                            index\n                        )\n                        val className = ctMethod.declaringClass.name\n                        val matched = cname != null && matchClass(\n                            ctMethod.name,\n                            ctMethod.methodInfo.descriptor,\n                            className,\n                            cname,\n                            clazz.classPool\n                        )\n                        if (matched) {\n                            return true\n                        }\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    private fun matchClass(\n        methodName: String,\n        methodDescriptor: String,\n        classname: String,\n        name: String,\n        pool: ClassPool\n    ): Boolean {\n        if (classname == name)\n            return true\n\n        try {\n            val clazz = pool.get(name)\n            val declClazz = pool.get(classname)\n            if (clazz.subtypeOf(declClazz))\n                try {\n                    val m = clazz.getMethod(methodName, methodDescriptor)\n                    return m.declaringClass.name == classname\n                } catch (e: NotFoundException) {\n                    // maybe the original method has been removed.\n                    return true\n                }\n\n        } catch (e: NotFoundException) {\n            return false\n        }\n\n        return false\n    }\n\n    /**\n     * 查找目标class是否存在目标构造器的调用\n     */\n    fun matchConstructorCallInClass(name: String, clazz: CtClass): Boolean {\n        for (methodInfo in clazz.classFile2.methods) {\n            methodInfo as MethodInfo\n            val codeAttr: CodeAttribute? = methodInfo.codeAttribute\n            val constPool = methodInfo.constPool\n            if (codeAttr != null) {\n                val iterator = codeAttr.iterator()\n                while (iterator.hasNext()) {\n                    val pos = iterator.next()\n                    val c = iterator.byteAt(pos)\n                    if (c == Opcode.INVOKESPECIAL) {\n                        val index = iterator.u16bitAt(pos + 1)\n                        val result = constPool.isConstructor(name, index)\n                        return result != 0\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/kotlin/com/tencent/shadow/core/transform_kit/FilterRefClassesTest.kt",
    "content": "package com.tencent.shadow.core.transform_kit\n\nimport org.junit.Assert\nimport org.junit.Test\n\nclass FilterRefClassesTest : AbstractTransformTest() {\n\n\n    /**\n     * 测试Transform时如果想修改一个对象方法调用时，\n     * 如果这个方法来自于其父类，是否可以通过过来父类来找出需要修改的类，\n     * 以便优化Transform速度。\n     * 结论是不行，直接调用父类方法时并不需要引用父类。\n     */\n    @Test\n    fun testCallSuperMethodWithoutSuperClass() {\n        val targetClass = sLoader[\"test.override.UseFooAsSuperSuper\"]\n\n        val allAppClass = setOf(targetClass)\n        val filterRefClasses =\n            SpecificTransform.filterRefClasses(allAppClass, listOf(\"test.override.SuperSuper\"))\n\n        Assert.assertFalse(\n            \"直接调用父类方法时并不需要引用父类\",\n            filterRefClasses.contains(targetClass)\n        )\n    }\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/kotlin/com/tencent/shadow/core/transform_kit/OverrideCheckTest.kt",
    "content": "package com.tencent.shadow.core.transform_kit\n\nimport javassist.ClassPool\nimport javassist.CtClass\nimport org.apache.commons.io.FileUtils\nimport org.junit.Assert\nimport org.junit.Before\nimport org.junit.Test\nimport java.io.File\n\nclass OverrideCheckTest : AbstractTransformTest() {\n    private lateinit var inputClasses: Array<CtClass>\n    private val overrideCheck = OverrideCheck()\n    private lateinit var overrideMap: Map<String, Collection<Method_OriginalDeclaringClass>>\n    private lateinit var errorResult: Map<String, List<Method_OriginalDeclaringClass>>\n\n    @Before\n    fun setUp() {\n        val buildDir = File(WRITE_FILE_DIR)\n        if (buildDir.exists()) {\n            FileUtils.cleanDirectory(buildDir)\n        }\n\n        inputClasses = ClassPool(true).get(\n            arrayOf(\n                \"test.override.Bar\",\n                \"test.override.Foo\"\n            )\n        )\n        overrideCheck.prepare(inputClasses.toSet())\n        overrideMap = overrideCheck.getOverrideMethods()\n        replaceClass(inputClasses)\n        errorResult = overrideCheck.check(dLoader, inputClasses.map { it.name })\n    }\n\n    private fun replaceClass(inputClasses: Array<CtClass>) {\n        inputClasses.forEach {\n            it.replaceClassName(\"test.override.Foo\", \"test.override.Foo_\")\n            it.replaceClassName(\"test.override.Arg\", \"test.override.NewArg\")\n            it.replaceClassName(\"test.override.Super\", \"test.override.NewSuper\")\n            it.writeFile(WRITE_FILE_DIR)\n        }\n    }\n\n    @Test\n    fun testFooFindAll() {\n        val name = \"test.override.Foo\"\n        findAllOverrideMethods(name)\n    }\n\n    @Test\n    fun testFooFindError() {\n        val name = \"test.override.Foo_\"\n        Assert.assertTrue(errorResult.contains(name))\n        val error = errorResult[name]!!\n        Assert.assertEquals(1, error.size)\n        Assert.assertTrue(\n            error.any {\n                it.first.name == \"s2\"\n            }\n        )\n    }\n\n    @Test\n    fun testBarFindAll() {\n        val name = \"test.override.Bar\"\n        findAllOverrideMethods(name)\n    }\n\n    @Test\n    fun testBarFindError() {\n        val name = \"test.override.Bar\"\n        Assert.assertFalse(errorResult.contains(name))\n    }\n\n    private fun findAllOverrideMethods(name: String) {\n        val overrideMethods = overrideMap[name]!!\n        Assert.assertTrue(\n            overrideMethods.any {\n                it.first.name == \"ss1\"\n            }\n        )\n\n        Assert.assertTrue(\n            overrideMethods.any {\n                it.first.name == \"s1\"\n            }\n        )\n\n        Assert.assertTrue(\n            overrideMethods.any {\n                it.first.name == \"s2\"\n            }\n        )\n    }\n\n}"
  },
  {
    "path": "projects/sdk/core/transform-kit/src/test/kotlin/com/tencent/shadow/core/transform_kit/RedirectMethodCallToStaticTest.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.core.transform_kit\n\nimport javassist.CodeConverter\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n\nclass RedirectMethodCallToStaticTest : AbstractTransformTest() {\n\n    @Test\n    fun redirectMethodCallToStaticMethodCall() {\n        val targetClass = sLoader[\"test.MethodRedirectToStatic\"]\n        val staticClass = sLoader[\"test.MethodRedirectToStatic2\"]\n\n        val targetMethod = targetClass.getDeclaredMethod(\"add\")\n        val staticMethod = staticClass.getDeclaredMethod(\"add2\")\n        val conv = CodeConverter()\n\n        conv.redirectMethodCallToStatic(targetMethod, staticMethod)\n        targetClass.instrument(conv)\n        targetClass.writeFile(WRITE_FILE_DIR)\n\n        val obj = make(targetClass.name)\n        assertEquals(30, invoke(obj, \"test\"))\n    }\n}"
  },
  {
    "path": "projects/sdk/core/utils/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/core/utils/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    testImplementation \"junit:junit:$junit_version\"\n\n    // https://mvnrepository.com/artifact/commons-io/commons-io (requires Java 8)\n    testImplementation \"commons-io:commons-io:$commons_io_jvm_version\"\n}\n"
  },
  {
    "path": "projects/sdk/core/utils/src/main/java/com/tencent/shadow/core/utils/Md5.java",
    "content": "package com.tencent.shadow.core.utils;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.math.BigInteger;\nimport java.security.MessageDigest;\n\npublic class Md5 {\n    /**\n     * 获取 文件的md5\n     * 计算方式性能优化\n     * 参考：https://juejin.im/post/583e2172128fe1006bf66bc8#heading-9\n     */\n    public static String md5File(File file) {\n        MessageDigest messageDigest;\n        RandomAccessFile randomAccessFile = null;\n        try {\n            messageDigest = MessageDigest.getInstance(\"MD5\");\n            randomAccessFile = new RandomAccessFile(file, \"r\");\n            byte[] bytes = new byte[2 * 1024 * 1024];\n            int len = 0;\n            while ((len = randomAccessFile.read(bytes)) != -1) {\n                messageDigest.update(bytes, 0, len);\n            }\n            BigInteger bigInt = new BigInteger(1, messageDigest.digest());\n            StringBuilder md5 = new StringBuilder(bigInt.toString(16));\n            while (md5.length() < 32) {\n                md5.insert(0, \"0\");\n            }\n            return md5.toString();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            closeQuietly(randomAccessFile);\n        }\n    }\n\n    static void closeQuietly(final Closeable closeable) {\n        try {\n            if (closeable != null) {\n                closeable.close();\n            }\n        } catch (final IOException ioe) {\n            // ignore\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/core/utils/src/test/java/com/tencent/shadow/core/utils/Md5Test.java",
    "content": "package com.tencent.shadow.core.utils;\n\nimport org.apache.commons.io.FileUtils;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class Md5Test {\n\n    @Test(expected = RuntimeException.class)\n    public void nullAsFile() {\n        Md5.md5File(null);\n    }\n\n    @Test\n    public void emptyFile() throws IOException {\n        File tempFile = File.createTempFile(\"Md5Test\", \"emptyFile\");\n        try {\n            String actual = Md5.md5File(tempFile);\n            assertEquals(\"d41d8cd98f00b204e9800998ecf8427e\", actual);\n        } finally {\n            FileUtils.delete(tempFile);\n        }\n    }\n\n    @Test\n    public void smallFile() throws IOException {\n        File tempFile = File.createTempFile(\"Md5Test\", \"smallFile\");\n        try {\n            byte[] bytes = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n            FileUtils.writeByteArrayToFile(tempFile, bytes);\n\n            String actual = Md5.md5File(tempFile);\n            assertEquals(\"1bdd36b0a024c90db383512607293692\", actual);\n        } finally {\n            FileUtils.delete(tempFile);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.gradletasknamecache\n\n"
  },
  {
    "path": "projects/sdk/dynamic/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\n//buildscript不能从其他gradle文件中apply，所以这段buildscript脚本存在于多个子构建中。\n//请更新buildscript时同步更新。\nbuildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$build_gradle_version\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        classpath 'com.tencent.shadow.coding:common-jar-settings'\n    }\n}\napply from: '../../../buildScripts/gradle/common.gradle'\n\nallprojects {\n    group 'com.tencent.shadow.dynamic'\n}\n\ntasks.create('test').dependsOn subprojects.collect { it.getTasksByName('test', false) }\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/.gitignore",
    "content": "/build\n*.iml"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    implementation 'com.tencent.shadow.core:utils'\n    compileOnly 'com.tencent.shadow.core:common'\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/src/main/java/com/tencent/shadow/dynamic/apk/ApkClassLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.apk;\n\nimport android.os.Build;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nimport dalvik.system.DexClassLoader;\n\n/**\n * Apk插件加载专用ClassLoader\n * <p>\n * 将宿主apk和插件apk隔离。但例外的是,插件可以从宿主apk中加载到约定的接口。\n * 这样隔离的目的是让宿主apk中的类可以通过约定的接口使用插件apk中的实现。而插件中的类不会使用到和宿主同名的类。\n * <p>\n * 如果目标类符合构造时传入的包名,则从parent ClassLoader中查找,否则先从自己的dexPath中查找,如果找不到,则再从\n * parent的parent ClassLoader中查找。\n *\n * @author cubershi\n */\npublic class ApkClassLoader extends DexClassLoader {\n    private ClassLoader mGrandParent;\n    private final String[] mInterfacePackageNames;\n\n    public ApkClassLoader(InstalledApk installedApk,\n                          ClassLoader parent, String[] mInterfacePackageNames, int grandTimes) {\n        super(installedApk.apkFilePath, installedApk.oDexPath, installedApk.libraryPath, parent);\n        ClassLoader grand = parent;\n        for (int i = 0; i < grandTimes; i++) {\n            grand = grand.getParent();\n        }\n        mGrandParent = grand;\n        this.mInterfacePackageNames = mInterfacePackageNames;\n    }\n\n    @Override\n    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {\n        String packageName;\n        int dot = className.lastIndexOf('.');\n        if (dot != -1) {\n            packageName = className.substring(0, dot);\n        } else {\n            packageName = \"\";\n        }\n\n        boolean isInterface = false;\n        for (String interfacePackageName : mInterfacePackageNames) {\n            if (packageName.equals(interfacePackageName)) {\n                isInterface = true;\n                break;\n            }\n        }\n\n        if (isInterface) {\n            return super.loadClass(className, resolve);\n        } else {\n            Class<?> clazz = findLoadedClass(className);\n\n            if (clazz == null) {\n                ClassNotFoundException suppressed = null;\n                try {\n                    clazz = findClass(className);\n                } catch (ClassNotFoundException e) {\n                    suppressed = e;\n                }\n\n                if (clazz == null) {\n                    try {\n                        clazz = mGrandParent.loadClass(className);\n                    } catch (ClassNotFoundException e) {\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n                            e.addSuppressed(suppressed);\n                        }\n                        throw e;\n                    }\n                }\n            }\n\n            return clazz;\n        }\n    }\n\n    /**\n     * 从apk中读取接口的实现\n     *\n     * @param clazz     接口类\n     * @param className 实现类的类名\n     * @param <T>       接口类型\n     * @return 所需接口\n     * @throws Exception\n     */\n    public <T> T getInterface(Class<T> clazz, String className) throws Exception {\n        try {\n            Class<?> interfaceImplementClass = loadClass(className);\n            Object interfaceImplement = interfaceImplementClass.newInstance();\n            return clazz.cast(interfaceImplement);\n        } catch (ClassNotFoundException | InstantiationException\n                | ClassCastException | IllegalAccessException e) {\n            throw new Exception(e);\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/src/main/java/com/tencent/shadow/dynamic/apk/ChangeApkContextWrapper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.apk;\n\nimport static android.content.pm.PackageManager.GET_META_DATA;\n\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.view.LayoutInflater;\n\n/**\n * 修改Context的apk路径的Wrapper。可将原Context的Resource和ClassLoader重新修改为新的Apk。\n */\npublic class ChangeApkContextWrapper extends ContextWrapper {\n\n    private Resources mResources;\n\n    private LayoutInflater mLayoutInflater;\n\n    final private ClassLoader mClassloader;\n\n    private Resources.Theme mTheme;\n\n    public ChangeApkContextWrapper(Context base, String apkPath, ClassLoader mClassloader) {\n        super(base);\n        this.mClassloader = mClassloader;\n        mResources = createResources(apkPath, base);\n    }\n\n    private Resources createResources(String apkPath, Context base) {\n        PackageManager packageManager = base.getPackageManager();\n        PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(apkPath, GET_META_DATA);\n        packageArchiveInfo.applicationInfo.publicSourceDir = apkPath;\n        packageArchiveInfo.applicationInfo.sourceDir = apkPath;\n        try {\n            return packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo);\n        } catch (PackageManager.NameNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n\n    }\n\n    @Override\n    public AssetManager getAssets() {\n        return mResources.getAssets();\n    }\n\n    @Override\n    public Resources getResources() {\n        return mResources;\n    }\n\n    @Override\n    public Resources.Theme getTheme() {\n        // 模仿android.view.ContextThemeWrapper#initializeTheme\n        if (mTheme == null) {\n            Resources.Theme newTheme = mResources.newTheme();\n            final Resources.Theme theme = getBaseContext().getTheme();\n            if (theme != null) {\n                newTheme.setTo(theme);\n            }\n            mTheme = newTheme;\n        }\n        return mTheme;\n    }\n\n    @Override\n    public Object getSystemService(String name) {\n        if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {\n            if (mLayoutInflater == null) {\n                LayoutInflater layoutInflater = (LayoutInflater) super.getSystemService(name);\n                mLayoutInflater = layoutInflater.cloneInContext(this);\n            }\n            return mLayoutInflater;\n        }\n        return super.getSystemService(name);\n    }\n\n    @Override\n    public ClassLoader getClassLoader() {\n        return mClassloader;\n    }\n}\n\n\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-apk/src/main/java/com/tencent/shadow/dynamic/apk/ImplLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.apk;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nimport java.lang.reflect.Field;\n\nimport dalvik.system.DexClassLoader;\n\npublic abstract class ImplLoader {\n    private static final String WHITE_LIST_CLASS_NAME = \"com.tencent.shadow.dynamic.impl.WhiteList\";\n    private static final String WHITE_LIST_FIELD_NAME = \"sWhiteList\";\n\n    protected abstract String[] getCustomWhiteList();\n\n    public String[] loadWhiteList(InstalledApk installedApk) {\n        return loadWhiteList(installedApk, WHITE_LIST_CLASS_NAME, WHITE_LIST_FIELD_NAME);\n    }\n\n    public String[] loadWhiteList(InstalledApk installedApk, String whiteListClassName, String whiteListFieldName) {\n        DexClassLoader dexClassLoader = new DexClassLoader(\n                installedApk.apkFilePath,\n                installedApk.oDexPath,\n                installedApk.libraryPath,\n                getClass().getClassLoader()\n        );\n\n        String[] whiteList = null;\n        try {\n            Class<?> whiteListClass = dexClassLoader.loadClass(whiteListClassName);\n            Field whiteListField = whiteListClass.getDeclaredField(whiteListFieldName);\n            Object o = whiteListField.get(null);\n            whiteList = (String[]) o;\n        } catch (ClassNotFoundException ignored) {\n        } catch (NoSuchFieldException e) {\n            throw new RuntimeException(e);\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n\n        String[] interfaces;\n        if (whiteList != null) {\n            interfaces = concatenate(getCustomWhiteList(), whiteList);\n        } else {\n            interfaces = getCustomWhiteList();\n        }\n        return interfaces;\n    }\n\n    private static String[] concatenate(String[] a, String[] b) {\n        int aLen = a.length;\n        int bLen = b.length;\n        String[] c = new String[aLen + bLen];\n        System.arraycopy(a, 0, c, 0, aLen);\n        System.arraycopy(b, 0, c, aLen, bLen);\n        return c;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/.gitignore",
    "content": "/build\n*.iml"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    implementation 'com.tencent.shadow.core:utils'\n    compileOnly 'com.tencent.shadow.core:common'\n    api project(':dynamic-apk')\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/BasePluginProcessService.java",
    "content": "package com.tencent.shadow.dynamic.host;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\nabstract public class BasePluginProcessService extends Service {\n\n    protected final Logger mLogger = LoggerFactory.getLogger(this.getClass());\n\n    /**\n     * PPS应该代表插件进程的生命周期。插件进程应该由PPS启动而启动。\n     * 所以不应该出现在同一个插件进程有两个PPS对象的情况。\n     * 如果出现，将会重复加载Loader、Runtime、业务等插件，进而出现非常奇怪的异常。\n     * 因此，用这样一个静态变量检测出这种情况。PPS不能死后重新创建。需要在上层合理设计保持PPS始终存活。\n     */\n    private static Object sSingleInstanceFlag = null;\n\n    @Override\n    public void onCreate() {\n        if (sSingleInstanceFlag == null) {\n            sSingleInstanceFlag = new Object();\n        } else {\n            throw new IllegalStateException(\"PPS出现多实例\");\n        }\n        super.onCreate();\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onCreate:\" + this);\n        }\n    }\n\n    @Override\n    public boolean onUnbind(Intent intent) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onUnbind:\" + this);\n        }\n        return super.onUnbind(intent);\n    }\n\n    @Override\n    public void onRebind(Intent intent) {\n        super.onRebind(intent);\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onRebind:\" + this);\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onDestroy:\" + this);\n        }\n    }\n\n    @Override\n    public void onTaskRemoved(Intent rootIntent) {\n        super.onTaskRemoved(rootIntent);\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onTaskRemoved:\" + this);\n        }\n    }\n\n    public static class ActivityHolder implements Application.ActivityLifecycleCallbacks {\n\n        private List<Activity> mActivities = new LinkedList<>();\n\n        void finishAll() {\n            for (Activity activity : mActivities) {\n                activity.finish();\n            }\n        }\n\n        @Override\n        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n            mActivities.add(activity);\n        }\n\n        @Override\n        public void onActivityDestroyed(Activity activity) {\n            mActivities.remove(activity);\n        }\n\n        @Override\n        public void onActivityStarted(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityResumed(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityStopped(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/BinderUuidManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.IBinder;\nimport android.os.Parcel;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nclass BinderUuidManager implements UuidManager {\n    private IBinder mRemote;\n\n    BinderUuidManager(IBinder remote) {\n        mRemote = remote;\n    }\n\n    private void checkException(Parcel _reply) throws FailedException, NotFoundException {\n        int i = _reply.readInt();\n        if (i == UuidManager.TRANSACTION_CODE_FAILED_EXCEPTION) {\n            throw new FailedException(_reply);\n        } else if (i == UuidManager.TRANSACTION_CODE_NOT_FOUND_EXCEPTION) {\n            throw new NotFoundException(_reply);\n        } else if (i != UuidManager.TRANSACTION_CODE_NO_EXCEPTION) {\n            throw new RuntimeException(\"不认识的Code==\" + i);\n        }\n    }\n\n    @Override\n    public InstalledApk getPlugin(String uuid, String partKey) throws android.os.RemoteException, FailedException, NotFoundException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        InstalledApk _result;\n        try {\n            _data.writeInterfaceToken(UuidManager.DESCRIPTOR);\n            _data.writeString(uuid);\n            _data.writeString(partKey);\n            mRemote.transact(UuidManager.TRANSACTION_getPlugin, _data, _reply, 0);\n            checkException(_reply);\n            if ((0 != _reply.readInt())) {\n                _result = InstalledApk.CREATOR.createFromParcel(_reply);\n            } else {\n                _result = null;\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public InstalledApk getPluginLoader(String uuid) throws android.os.RemoteException, NotFoundException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        InstalledApk _result;\n        try {\n            _data.writeInterfaceToken(UuidManager.DESCRIPTOR);\n            _data.writeString(uuid);\n            mRemote.transact(UuidManager.TRANSACTION_getPluginLoader, _data, _reply, 0);\n            checkException(_reply);\n            if ((0 != _reply.readInt())) {\n                _result = InstalledApk.CREATOR.createFromParcel(_reply);\n            } else {\n                _result = null;\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public InstalledApk getRuntime(String uuid) throws android.os.RemoteException, NotFoundException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        InstalledApk _result;\n        try {\n            _data.writeInterfaceToken(UuidManager.DESCRIPTOR);\n            _data.writeString(uuid);\n            mRemote.transact(UuidManager.TRANSACTION_getRuntime, _data, _reply, 0);\n            checkException(_reply);\n            if ((0 != _reply.readInt())) {\n                _result = InstalledApk.CREATOR.createFromParcel(_reply);\n            } else {\n                _result = null;\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/DynamicPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.text.TextUtils;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.io.File;\n\nimport static com.tencent.shadow.core.utils.Md5.md5File;\n\npublic final class DynamicPluginManager implements PluginManager {\n\n    final private PluginManagerUpdater mUpdater;\n    private PluginManagerImpl mManagerImpl;\n    private String mCurrentImplMd5;\n    private static final Logger mLogger = LoggerFactory.getLogger(DynamicPluginManager.class);\n\n    public DynamicPluginManager(PluginManagerUpdater updater) {\n        if (updater.getLatest() == null) {\n            throw new IllegalArgumentException(\"构造DynamicPluginManager时传入的PluginManagerUpdater\" +\n                    \"必须已经已有本地文件，即getLatest()!=null\");\n        }\n        mUpdater = updater;\n    }\n\n    @Override\n    public void enter(Context context, long fromId, Bundle bundle, EnterCallback callback) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"enter fromId:\" + fromId + \" callback:\" + callback);\n        }\n        updateManagerImpl(context);\n        mManagerImpl.enter(context, fromId, bundle, callback);\n        mUpdater.update();\n    }\n\n    public void release() {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"release\");\n        }\n        if (mManagerImpl != null) {\n            mManagerImpl.onDestroy();\n            mManagerImpl = null;\n        }\n    }\n\n    private void updateManagerImpl(Context context) {\n        File latestManagerImplApk = mUpdater.getLatest();\n        String md5 = md5File(latestManagerImplApk);\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"TextUtils.equals(mCurrentImplMd5, md5) : \" + (TextUtils.equals(mCurrentImplMd5, md5)));\n        }\n        if (!TextUtils.equals(mCurrentImplMd5, md5)) {\n            ManagerImplLoader implLoader = new ManagerImplLoader(context, latestManagerImplApk);\n            PluginManagerImpl newImpl = implLoader.load();\n            Bundle state;\n            if (mManagerImpl != null) {\n                state = new Bundle();\n                mManagerImpl.onSaveInstanceState(state);\n                mManagerImpl.onDestroy();\n            } else {\n                state = null;\n            }\n            newImpl.onCreate(state);\n            mManagerImpl = newImpl;\n            mCurrentImplMd5 = md5;\n        }\n    }\n\n    public PluginManager getManagerImpl() {\n        return mManagerImpl;\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/DynamicRuntime.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.text.TextUtils;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.io.File;\nimport java.lang.reflect.Field;\n\nimport dalvik.system.BaseDexClassLoader;\n\n/**\n * 将runtime apk加载到DexPathClassLoader，形成如下结构的classLoader树结构\n * ---BootClassLoader\n * ----RuntimeClassLoader\n * ------PathClassLoader\n */\npublic class DynamicRuntime {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(DynamicRuntime.class);\n\n    private static final String SP_NAME = \"ShadowRuntimeLoader\";\n\n    private static final String KEY_RUNTIME_APK = \"KEY_RUNTIME_APK\";\n    private static final String KEY_RUNTIME_ODEX = \"KEY_RUNTIME_ODEX\";\n    private static final String KEY_RUNTIME_LIB = \"KEY_RUNTIME_LIB\";\n\n    /**\n     * 加载runtime apk\n     *\n     * @return true 加载了新的runtime\n     */\n    public static boolean loadRuntime(InstalledApk installedRuntimeApk) {\n        ClassLoader contextClassLoader = DynamicRuntime.class.getClassLoader();\n        RuntimeClassLoader runtimeClassLoader = getRuntimeClassLoader();\n        if (runtimeClassLoader != null) {\n            String apkPath = runtimeClassLoader.apkPath;\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"last apkPath:\" + apkPath + \" new apkPath:\" + installedRuntimeApk.apkFilePath);\n            }\n            if (TextUtils.equals(apkPath, installedRuntimeApk.apkFilePath)) {\n                //已经加载相同版本的runtime了,不需要加载\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"已经加载相同apkPath的runtime了,不需要加载\");\n                }\n                return false;\n            } else {\n                //版本不一样，说明要更新runtime，先恢复正常的classLoader结构\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"加载不相同apkPath的runtime了,先恢复classLoader树结构\");\n                }\n                try {\n                    recoveryClassLoader();\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n        //正常处理，将runtime 挂到pathclassLoader之上\n        try {\n            hackParentToRuntime(installedRuntimeApk, contextClassLoader);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        return true;\n    }\n\n\n    private static void recoveryClassLoader() throws Exception {\n        ClassLoader contextClassLoader = DynamicRuntime.class.getClassLoader();\n        ClassLoader child = contextClassLoader;\n        ClassLoader tmpClassLoader = contextClassLoader.getParent();\n        while (tmpClassLoader != null) {\n            if (tmpClassLoader instanceof RuntimeClassLoader) {\n                hackParentClassLoader(child, tmpClassLoader.getParent());\n                return;\n            }\n            child = tmpClassLoader;\n            tmpClassLoader = tmpClassLoader.getParent();\n        }\n    }\n\n\n    private static RuntimeClassLoader getRuntimeClassLoader() {\n        ClassLoader contextClassLoader = DynamicRuntime.class.getClassLoader();\n        ClassLoader tmpClassLoader = contextClassLoader.getParent();\n        while (tmpClassLoader != null) {\n            if (tmpClassLoader instanceof RuntimeClassLoader) {\n                return (RuntimeClassLoader) tmpClassLoader;\n            }\n            tmpClassLoader = tmpClassLoader.getParent();\n        }\n        return null;\n    }\n\n\n    private static void hackParentToRuntime(InstalledApk installedRuntimeApk, ClassLoader contextClassLoader) throws Exception {\n        RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(installedRuntimeApk.apkFilePath, installedRuntimeApk.oDexPath,\n                installedRuntimeApk.libraryPath, contextClassLoader.getParent());\n        hackParentClassLoader(contextClassLoader, runtimeClassLoader);\n    }\n\n\n    /**\n     * 修改ClassLoader的parent\n     *\n     * @param classLoader          需要修改的ClassLoader\n     * @param newParentClassLoader classLoader的新的parent\n     * @throws Exception 失败时抛出\n     */\n    static void hackParentClassLoader(ClassLoader classLoader,\n                                      ClassLoader newParentClassLoader) throws Exception {\n        Field field = getParentField();\n        if (field == null) {\n            throw new RuntimeException(\"在ClassLoader.class中没找到类型为ClassLoader的parent域\");\n        }\n        field.setAccessible(true);\n        field.set(classLoader, newParentClassLoader);\n    }\n\n    /**\n     * 安全地获取到ClassLoader类的parent域\n     *\n     * @return ClassLoader类的parent域.或不能通过反射访问该域时返回null.\n     */\n    private static Field getParentField() {\n        ClassLoader classLoader = DynamicRuntime.class.getClassLoader();\n        ClassLoader parent = classLoader.getParent();\n        Field field = null;\n        for (Field f : ClassLoader.class.getDeclaredFields()) {\n            try {\n                boolean accessible = f.isAccessible();\n                f.setAccessible(true);\n                Object o = f.get(classLoader);\n                f.setAccessible(accessible);\n                if (o == parent) {\n                    field = f;\n                    break;\n                }\n            } catch (IllegalAccessException ignore) {\n            }\n        }\n        return field;\n    }\n\n    /**\n     * 重新恢复runtime\n     *\n     * @return true 进行了runtime恢复\n     */\n    public static boolean recoveryRuntime(Context context) {\n        InstalledApk installedApk = getLastRuntimeInfo(context);\n        if (installedApk != null && new File(installedApk.apkFilePath).exists()) {\n            if (installedApk.oDexPath != null && !new File(installedApk.oDexPath).exists()) {\n                return false;\n            }\n            try {\n                hackParentToRuntime(installedApk, DynamicRuntime.class.getClassLoader());\n                return true;\n            } catch (Exception e) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"recoveryRuntime 错误\", e);\n                }\n                removeLastRuntimeInfo(context);\n            }\n        }\n        return false;\n    }\n\n    @SuppressLint(\"ApplySharedPref\")\n    public static void saveLastRuntimeInfo(Context context, InstalledApk installedRuntimeApk) {\n        SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);\n        preferences.edit()\n                .putString(KEY_RUNTIME_APK, installedRuntimeApk.apkFilePath)\n                .putString(KEY_RUNTIME_ODEX, installedRuntimeApk.oDexPath)\n                .putString(KEY_RUNTIME_LIB, installedRuntimeApk.libraryPath)\n                .commit();\n    }\n\n    private static InstalledApk getLastRuntimeInfo(Context context) {\n        SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);\n        String apkFilePath = preferences.getString(KEY_RUNTIME_APK, null);\n        String oDexPath = preferences.getString(KEY_RUNTIME_ODEX, null);\n        String libraryPath = preferences.getString(KEY_RUNTIME_LIB, null);\n\n        if (apkFilePath == null) {\n            return null;\n        } else {\n            return new InstalledApk(apkFilePath, oDexPath, libraryPath);\n        }\n    }\n\n    @SuppressLint(\"ApplySharedPref\")\n    private static void removeLastRuntimeInfo(Context context) {\n        SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);\n        preferences.edit()\n                .remove(KEY_RUNTIME_APK)\n                .remove(KEY_RUNTIME_ODEX)\n                .remove(KEY_RUNTIME_LIB)\n                .commit();\n    }\n\n\n    static class RuntimeClassLoader extends BaseDexClassLoader {\n        /**\n         * 加载的apk路径\n         */\n        private String apkPath;\n\n\n        RuntimeClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {\n            super(dexPath, optimizedDirectory == null ? null : new File(optimizedDirectory), librarySearchPath, parent);\n            this.apkPath = dexPath;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/EnterCallback.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.view.View;\n\npublic interface EnterCallback {\n\n    void onShowLoadingView(View view);\n\n    void onCloseLoadingView();\n\n    void onEnterComplete();\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/FailedException.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.os.RemoteException;\n\npublic class FailedException extends Exception implements Parcelable {\n    public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;\n    public static final int ERROR_CODE_RUNTIME_EXCEPTION = 2;\n    public static final int ERROR_CODE_FILE_NOT_FOUND_EXCEPTION = 3;\n    public static final int ERROR_CODE_UUID_MANAGER_NULL_EXCEPTION = 4;\n    public static final int ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION = 5;\n    public static final int ERROR_CODE_RESET_UUID_EXCEPTION = 6;\n    public static final int ERROR_CODE_RELOAD_RUNTIME_EXCEPTION = 7;\n    public static final int ERROR_CODE_RELOAD_LOADER_EXCEPTION = 8;\n\n    public final int errorCode;\n    public final String errorMessage;\n\n    public FailedException(RemoteException e) {\n        this.errorCode = ERROR_CODE_REMOTE_EXCEPTION;\n        this.errorMessage = e.getClass().getSimpleName() + \":\" + e.getMessage();\n    }\n\n    public FailedException(RuntimeException e) {\n        this.errorCode = ERROR_CODE_RUNTIME_EXCEPTION;\n        this.errorMessage = e.getClass().getSimpleName() + \":\" + e.getMessage();\n    }\n\n    public FailedException(int errorCode, String errorMessage) {\n        this.errorCode = errorCode;\n        this.errorMessage = errorMessage;\n    }\n\n    protected FailedException(Parcel in) {\n        errorCode = in.readInt();\n        errorMessage = in.readString();\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(errorCode);\n        dest.writeString(errorMessage);\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<FailedException> CREATOR = new Creator<FailedException>() {\n        @Override\n        public FailedException createFromParcel(Parcel in) {\n            return new FailedException(in);\n        }\n\n        @Override\n        public FailedException[] newArray(int size) {\n            return new FailedException[size];\n        }\n    };\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/LoaderFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\n\npublic interface LoaderFactory {\n    PluginLoaderImpl buildLoader(String uuid, Context context);\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/LoaderImplLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.dynamic.apk.ApkClassLoader;\nimport com.tencent.shadow.dynamic.apk.ImplLoader;\n\nfinal class LoaderImplLoader extends ImplLoader {\n    /**\n     * 加载{@link #sLoaderFactoryImplClassName}时\n     * 需要从宿主PathClassLoader（含双亲委派）中加载的类\n     */\n    private static final String[] sInterfaces = new String[]{\n            //当runtime是动态加载的时候，runtime的ClassLoader是PathClassLoader的parent，\n            // 所以不需要写在这个白名单里。但是写在这里不影响，也可以兼容runtime打包在宿主的情况。\n            \"com.tencent.shadow.core.runtime.container\",\n            \"com.tencent.shadow.dynamic.host\",\n            \"com.tencent.shadow.core.common\"\n    };\n\n    private final static String sLoaderFactoryImplClassName\n            = \"com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl\";\n\n    PluginLoaderImpl load(InstalledApk installedApk, String uuid, Context appContext) throws Exception {\n        ApkClassLoader pluginLoaderClassLoader = new ApkClassLoader(\n                installedApk,\n                LoaderImplLoader.class.getClassLoader(),\n                loadWhiteList(installedApk),\n                1\n        );\n        LoaderFactory loaderFactory = pluginLoaderClassLoader.getInterface(\n                LoaderFactory.class,\n                sLoaderFactoryImplClassName\n        );\n\n        return loaderFactory.buildLoader(uuid, appContext);\n    }\n\n    @Override\n    protected String[] getCustomWhiteList() {\n        return sInterfaces;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/ManagerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\n\npublic interface ManagerFactory {\n    PluginManagerImpl buildManager(Context context);\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/ManagerImplLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.dynamic.apk.ApkClassLoader;\nimport com.tencent.shadow.dynamic.apk.ChangeApkContextWrapper;\nimport com.tencent.shadow.dynamic.apk.ImplLoader;\n\nimport java.io.File;\n\nfinal class ManagerImplLoader extends ImplLoader {\n    private static final String MANAGER_FACTORY_CLASS_NAME = \"com.tencent.shadow.dynamic.impl.ManagerFactoryImpl\";\n    private static final String[] REMOTE_PLUGIN_MANAGER_INTERFACES = new String[]\n            {\n                    \"com.tencent.shadow.core.common\",\n                    \"com.tencent.shadow.dynamic.host\"\n            };\n    final private Context applicationContext;\n    final private InstalledApk installedApk;\n\n    ManagerImplLoader(Context context, File apk) {\n        applicationContext = context.getApplicationContext();\n        File root = new File(applicationContext.getFilesDir(), \"ManagerImplLoader\");\n        File odexDir = new File(root, Long.toString(apk.lastModified(), Character.MAX_RADIX));\n        odexDir.mkdirs();\n        installedApk = new InstalledApk(apk.getAbsolutePath(), odexDir.getAbsolutePath(), null);\n    }\n\n    PluginManagerImpl load() {\n        ApkClassLoader apkClassLoader = new ApkClassLoader(\n                installedApk,\n                getClass().getClassLoader(),\n                loadWhiteList(installedApk),\n                1\n        );\n\n        Context pluginManagerContext = new ChangeApkContextWrapper(\n                applicationContext,\n                installedApk.apkFilePath,\n                apkClassLoader\n        );\n\n        try {\n            ManagerFactory managerFactory = apkClassLoader.getInterface(\n                    ManagerFactory.class,\n                    MANAGER_FACTORY_CLASS_NAME\n            );\n            return managerFactory.buildManager(pluginManagerContext);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n\n    @Override\n    protected String[] getCustomWhiteList() {\n        return REMOTE_PLUGIN_MANAGER_INTERFACES;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/NotFoundException.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic class NotFoundException extends Exception implements Parcelable {\n    public NotFoundException(String message) {\n        super(message);\n    }\n\n    protected NotFoundException(Parcel in) {\n        super(in.readString());\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(getMessage());\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<NotFoundException> CREATOR = new Creator<NotFoundException>() {\n        @Override\n        public NotFoundException createFromParcel(Parcel in) {\n            return new NotFoundException(in);\n        }\n\n        @Override\n        public NotFoundException[] newArray(int size) {\n            return new NotFoundException[size];\n        }\n    };\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PluginLoaderImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.IBinder;\n\npublic interface PluginLoaderImpl extends IBinder {\n    void setUuidManager(UuidManager uuidManager);\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.content.Context;\nimport android.os.Bundle;\n\n/**\n * 使用方持有的接口\n *\n * @author cubershi\n */\npublic interface PluginManager {\n\n    /**\n     * @param context  context\n     * @param fromId   标识本次请求的来源位置，用于区分入口\n     * @param bundle   参数列表\n     * @param callback 用于从PluginManager实现中返回View\n     */\n    void enter(Context context, long fromId, Bundle bundle, EnterCallback callback);\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PluginManagerImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.Bundle;\n\n/**\n * 实现方需要实现的接口\n *\n * @author cubershi\n */\npublic interface PluginManagerImpl extends PluginManager {\n\n    void onCreate(Bundle bundle);\n\n    void onSaveInstanceState(Bundle outState);\n\n    void onDestroy();\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PluginManagerUpdater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\n/**\n * PluginManager文件升级器\n * <p>\n * 注意这个类不负责什么时候该升级PluginManager，\n * 它只提供需要升级时的功能，如下载和向远端查询文件是否还可用。\n */\npublic interface PluginManagerUpdater {\n    /**\n     * @return <code>true</code>表示之前更新过程中意外中断了\n     */\n    boolean wasUpdating();\n\n    /**\n     * 更新\n     *\n     * @return 当前最新的PluginManager，可能是之前已经返回过的文件，但它是最新的了。\n     */\n    Future<File> update();\n\n    /**\n     * 获取本地最新可用的\n     *\n     * @return <code>null</code>表示本地没有可用的\n     */\n    File getLatest();\n\n    /**\n     * 查询是否可用\n     *\n     * @param file PluginManagerUpdater返回的file\n     * @return <code>true</code>表示可用，<code>false</code>表示不可用\n     */\n    Future<Boolean> isAvailable(File file);\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PluginProcessService.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.app.Application;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nimport java.io.File;\n\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_FILE_NOT_FOUND_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RELOAD_LOADER_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RELOAD_RUNTIME_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RESET_UUID_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RUNTIME_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_UUID_MANAGER_NULL_EXCEPTION;\n\n\npublic class PluginProcessService extends BasePluginProcessService {\n\n    private final PpsBinder mPpsControllerBinder = new PpsBinder(this);\n\n    static final ActivityHolder sActivityHolder = new ActivityHolder();\n\n    public static Application.ActivityLifecycleCallbacks getActivityHolder() {\n        return sActivityHolder;\n    }\n\n    public static PpsController wrapBinder(IBinder ppsBinder) {\n        return new PpsController(ppsBinder);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onBind:\" + this);\n        }\n        return mPpsControllerBinder;\n    }\n\n    private UuidManager mUuidManager;\n\n    private PluginLoaderImpl mPluginLoader;\n\n    private boolean mRuntimeLoaded = false;\n\n    /**\n     * 当前的Uuid。一旦设置不可修改。\n     */\n    private String mUuid = \"\";\n\n    private void setUuid(String uuid) throws FailedException {\n        if (mUuid.isEmpty()) {\n            mUuid = uuid;\n        } else if (!mUuid.equals(uuid)) {\n            throw new FailedException(ERROR_CODE_RESET_UUID_EXCEPTION, \"已设置过uuid==\" + mUuid + \"试图设置uuid==\" + uuid);\n        }\n    }\n\n    private void checkUuidManagerNotNull() throws FailedException {\n        if (mUuidManager == null) {\n            throw new FailedException(ERROR_CODE_UUID_MANAGER_NULL_EXCEPTION, \"mUuidManager == null\");\n        }\n    }\n\n    void loadRuntime(String uuid) throws FailedException {\n        checkUuidManagerNotNull();\n        setUuid(uuid);\n        if (mRuntimeLoaded) {\n            throw new FailedException(ERROR_CODE_RELOAD_RUNTIME_EXCEPTION\n                    , \"重复调用loadRuntime\");\n        }\n        try {\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"loadRuntime uuid:\" + uuid);\n            }\n            InstalledApk installedApk;\n            try {\n                installedApk = mUuidManager.getRuntime(uuid);\n            } catch (RemoteException e) {\n                throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());\n            } catch (NotFoundException e) {\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, \"uuid==\" + uuid + \"的Runtime没有找到。cause:\" + e.getMessage());\n            }\n\n            InstalledApk installedRuntimeApk = new InstalledApk(installedApk.apkFilePath, installedApk.oDexPath, installedApk.libraryPath);\n            boolean loaded = DynamicRuntime.loadRuntime(installedRuntimeApk);\n            if (loaded) {\n                DynamicRuntime.saveLastRuntimeInfo(this, installedRuntimeApk);\n            }\n            mRuntimeLoaded = true;\n        } catch (RuntimeException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadRuntime发生RuntimeException\", e);\n            }\n            throw new FailedException(e);\n        }\n    }\n\n    void loadPluginLoader(String uuid) throws FailedException {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadPluginLoader uuid:\" + uuid + \" mPluginLoader:\" + mPluginLoader);\n        }\n        checkUuidManagerNotNull();\n        setUuid(uuid);\n        if (mPluginLoader != null) {\n            throw new FailedException(ERROR_CODE_RELOAD_LOADER_EXCEPTION\n                    , \"重复调用loadPluginLoader\");\n        }\n        try {\n            InstalledApk installedApk;\n            try {\n                installedApk = mUuidManager.getPluginLoader(uuid);\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"取出uuid==\" + uuid + \"的Loader apk:\" + installedApk.apkFilePath);\n                }\n            } catch (RemoteException e) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"获取Loader Apk失败\", e);\n                }\n                throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());\n            } catch (NotFoundException e) {\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, \"uuid==\" + uuid + \"的PluginLoader没有找到。cause:\" + e.getMessage());\n            }\n            File file = new File(installedApk.apkFilePath);\n            if (!file.exists()) {\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, file.getAbsolutePath() + \"文件不存在\");\n            }\n\n            PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());\n            pluginLoader.setUuidManager(mUuidManager);\n            mPluginLoader = pluginLoader;\n        } catch (RuntimeException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadPluginLoader发生RuntimeException\", e);\n            }\n            throw new FailedException(e);\n        } catch (FailedException e) {\n            throw e;\n        } catch (Exception e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadPluginLoader发生Exception\", e);\n            }\n            String msg = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();\n            throw new FailedException(ERROR_CODE_RUNTIME_EXCEPTION, \"加载动态实现失败 cause：\" + msg);\n        }\n    }\n\n    void setUuidManager(UuidManager uuidManager) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"setUuidManager uuidManager==\" + uuidManager);\n        }\n        mUuidManager = uuidManager;\n        if (mPluginLoader != null) {\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"更新mPluginLoader的uuidManager\");\n            }\n            mPluginLoader.setUuidManager(uuidManager);\n        }\n    }\n\n    void exit() {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"exit \");\n        }\n        PluginProcessService.sActivityHolder.finishAll();\n        System.exit(0);\n        try {\n            wait();\n        } catch (InterruptedException ignored) {\n        }\n    }\n\n    PpsStatus getPpsStatus() {\n        return new PpsStatus(mUuid, mRuntimeLoaded, mPluginLoader != null, mUuidManager != null);\n    }\n\n    IBinder getPluginLoader() {\n        return mPluginLoader;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PpsBinder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.IBinder;\nimport android.os.Parcel;\n\nimport static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;\n\nclass PpsBinder extends android.os.Binder {\n    static final String DESCRIPTOR = PpsBinder.class.getName();\n\n    static final int TRANSACTION_CODE_NO_EXCEPTION = 0;\n    static final int TRANSACTION_CODE_FAILED_EXCEPTION = 1;\n\n    static final int TRANSACTION_loadRuntime = (FIRST_CALL_TRANSACTION);\n    static final int TRANSACTION_loadPluginLoader = (FIRST_CALL_TRANSACTION + 1);\n    static final int TRANSACTION_setUuidManager = (FIRST_CALL_TRANSACTION + 2);\n    static final int TRANSACTION_exit = (FIRST_CALL_TRANSACTION + 3);\n    static final int TRANSACTION_getPpsStatus = (FIRST_CALL_TRANSACTION + 4);\n    static final int TRANSACTION_getPluginLoader = (FIRST_CALL_TRANSACTION + 5);\n\n    private final PluginProcessService mPps;\n\n    PpsBinder(PluginProcessService pps) {\n        mPps = pps;\n    }\n\n    @Override\n    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {\n        switch (code) {\n            case INTERFACE_TRANSACTION: {\n                reply.writeString(DESCRIPTOR);\n                return true;\n            }\n            case TRANSACTION_loadRuntime: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0;\n                _arg0 = data.readString();\n                try {\n                    mPps.loadRuntime(_arg0);\n                    reply.writeInt(TRANSACTION_CODE_NO_EXCEPTION);\n                } catch (FailedException e) {\n                    reply.writeInt(TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case TRANSACTION_loadPluginLoader: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0;\n                _arg0 = data.readString();\n                try {\n                    mPps.loadPluginLoader(_arg0);\n                    reply.writeInt(TRANSACTION_CODE_NO_EXCEPTION);\n                } catch (FailedException e) {\n                    reply.writeInt(TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case TRANSACTION_setUuidManager: {\n                data.enforceInterface(DESCRIPTOR);\n                IBinder iBinder = data.readStrongBinder();\n                UuidManager uuidManager = iBinder != null ? new BinderUuidManager(iBinder) : null;\n                mPps.setUuidManager(uuidManager);\n                reply.writeNoException();\n                return true;\n            }\n            case TRANSACTION_exit: {\n                data.enforceInterface(DESCRIPTOR);\n                mPps.exit();\n                reply.writeNoException();\n                return true;\n            }\n            case TRANSACTION_getPpsStatus: {\n                data.enforceInterface(DESCRIPTOR);\n                PpsStatus ppsStatus = mPps.getPpsStatus();\n                reply.writeNoException();\n                ppsStatus.writeToParcel(reply, PARCELABLE_WRITE_RETURN_VALUE);\n                return true;\n            }\n            case TRANSACTION_getPluginLoader: {\n                data.enforceInterface(DESCRIPTOR);\n                IBinder pluginLoader = mPps.getPluginLoader();\n                reply.writeNoException();\n                reply.writeStrongBinder(pluginLoader);\n                return true;\n            }\n            default:\n                return false;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PpsController.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport static com.tencent.shadow.dynamic.host.PpsBinder.TRANSACTION_CODE_FAILED_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.PpsBinder.TRANSACTION_CODE_NO_EXCEPTION;\n\npublic class PpsController {\n    final private IBinder mRemote;\n\n    PpsController(IBinder remote) {\n        mRemote = remote;\n    }\n\n    public void loadRuntime(String uuid) throws RemoteException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            _data.writeString(uuid);\n            mRemote.transact(PpsBinder.TRANSACTION_loadRuntime, _data, _reply, 0);\n            int i = _reply.readInt();\n            if (i == TRANSACTION_CODE_FAILED_EXCEPTION) {\n                throw new FailedException(_reply);\n            } else if (i != TRANSACTION_CODE_NO_EXCEPTION) {\n                throw new RuntimeException(\"不认识的Code==\" + i);\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public void loadPluginLoader(String uuid) throws RemoteException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            _data.writeString(uuid);\n            mRemote.transact(PpsBinder.TRANSACTION_loadPluginLoader, _data, _reply, 0);\n            int i = _reply.readInt();\n            if (i == TRANSACTION_CODE_FAILED_EXCEPTION) {\n                throw new FailedException(_reply);\n            } else if (i != TRANSACTION_CODE_NO_EXCEPTION) {\n                throw new RuntimeException(\"不认识的Code==\" + i);\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public void setUuidManager(IBinder uuidManagerBinder) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            _data.writeStrongBinder(uuidManagerBinder);\n            mRemote.transact(PpsBinder.TRANSACTION_setUuidManager, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public void exit() throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            mRemote.transact(PpsBinder.TRANSACTION_exit, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public PpsStatus getPpsStatus() throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        PpsStatus _result;\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            mRemote.transact(PpsBinder.TRANSACTION_getPpsStatus, _data, _reply, 0);\n            _reply.readException();\n            _result = new PpsStatus(_reply);\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    public IBinder getPluginLoader() throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        IBinder _result;\n        try {\n            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);\n            mRemote.transact(PpsBinder.TRANSACTION_getPluginLoader, _data, _reply, 0);\n            _reply.readException();\n            _result = _reply.readStrongBinder();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/PpsStatus.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\npublic final class PpsStatus implements Parcelable {\n    final public String uuid;\n    final public boolean runtimeLoaded;\n    final public boolean loaderLoaded;\n    final public boolean uuidManagerSet;\n\n    PpsStatus(String uuid, boolean runtimeLoaded, boolean loaderLoaded, boolean uuidManagerSet) {\n        this.uuid = uuid;\n        this.runtimeLoaded = runtimeLoaded;\n        this.loaderLoaded = loaderLoaded;\n        this.uuidManagerSet = uuidManagerSet;\n    }\n\n    PpsStatus(Parcel in) {\n        uuid = in.readString();\n        runtimeLoaded = in.readByte() != 0;\n        loaderLoaded = in.readByte() != 0;\n        uuidManagerSet = in.readByte() != 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(uuid);\n        dest.writeByte((byte) (runtimeLoaded ? 1 : 0));\n        dest.writeByte((byte) (loaderLoaded ? 1 : 0));\n        dest.writeByte((byte) (uuidManagerSet ? 1 : 0));\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    public static final Creator<PpsStatus> CREATOR = new Creator<PpsStatus>() {\n        @Override\n        public PpsStatus createFromParcel(Parcel in) {\n            return new PpsStatus(in);\n        }\n\n        @Override\n        public PpsStatus[] newArray(int size) {\n            return new PpsStatus[size];\n        }\n    };\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host/src/main/java/com/tencent/shadow/dynamic/host/UuidManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.host;\n\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\npublic interface UuidManager {\n\n    int TRANSACTION_CODE_NO_EXCEPTION = 0;\n    int TRANSACTION_CODE_FAILED_EXCEPTION = 1;\n    int TRANSACTION_CODE_NOT_FOUND_EXCEPTION = 2;\n    String DESCRIPTOR = UuidManager.class.getName();\n    int TRANSACTION_getPlugin = (android.os.IBinder.FIRST_CALL_TRANSACTION);\n    int TRANSACTION_getPluginLoader = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);\n    int TRANSACTION_getRuntime = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);\n\n    InstalledApk getPlugin(String uuid, String partKey) throws RemoteException, NotFoundException, FailedException;\n\n    InstalledApk getPluginLoader(String uuid) throws RemoteException, NotFoundException, FailedException;\n\n    InstalledApk getRuntime(String uuid) throws RemoteException, NotFoundException, FailedException;\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    compileOnly 'com.tencent.shadow.core:common'\n    api project(':dynamic-host')\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/src/main/java/com/tencent/shadow/dynamic/host/MultiDynamicContainer.java",
    "content": "package com.tencent.shadow.dynamic.host;\n\nimport android.text.TextUtils;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\n\nimport java.io.File;\n\nimport dalvik.system.BaseDexClassLoader;\n\n/**\n * 将Container部分的hack到PathClassLoader之上，形成如下结构的classLoader树结构\n * ---BootClassLoader\n * ----ContainerClassLoader (可能有多个)\n * -----PathClassLoader\n */\npublic class MultiDynamicContainer {\n    private static final Logger mLogger = LoggerFactory.getLogger(MultiDynamicContainer.class);\n\n    /**\n     * hack ContainerClassLoader到PathClassLoader之上\n     * 1. ClassLoader树结构中可能包含多个ContainerClassLoader\n     * 2. 在hack时，需要提供containerKey作为该插件containerApk的标识\n     *\n     * @param containerKey 插件业务对应的key，不随插件版本变动\n     * @param containerApk 插件zip包中的runtimeApk\n     */\n    public static boolean loadContainerApk(String containerKey, InstalledApk containerApk) {\n        // 根据key去查找对应的ContainerClassLoader\n        ContainerClassLoader containerClassLoader = findContainerClassLoader(containerKey);\n        if (containerClassLoader != null) {\n            String apkFilePath = containerClassLoader.apkFilePath;\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"该containKey的apk已经加载过, containKey=\" + containerKey +\n                        \", last apkPath=\" + apkFilePath + \", new apkPath=\" + containerApk.apkFilePath);\n            }\n\n            if (TextUtils.equals(apkFilePath, containerApk.apkFilePath)) {\n                //已经加载相同版本的containerApk了,不需要加载\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"已经加载相同apkPath的containerApk了,不需要加载\");\n                }\n                return false;\n            } else {\n                // 同个插件的ContainerClassLoader版本不一样，说明要移除老的ContainerClassLoader，插入新的\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"加载不相同apkPath的containerApk了,先将老的移除\");\n                }\n                try {\n                    removeContainerClassLoader(containerClassLoader);\n                } catch (Exception e) {\n                    mLogger.error(\"移除老的containerApk失败\", e);\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n        // 将ContainerClassLoader hack到PathClassloader之上\n        try {\n            hackContainerClassLoader(containerKey, containerApk);\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"containerApk插入成功，containerKey=\" + containerKey + \", path=\" + containerApk.apkFilePath);\n            }\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        return true;\n    }\n\n    private static ContainerClassLoader findContainerClassLoader(String containerKey) {\n        ClassLoader current = MultiDynamicContainer.class.getClassLoader();\n        ClassLoader parent = current.getParent();\n        while (parent != null) {\n            if (parent instanceof ContainerClassLoader) {\n                ContainerClassLoader item = (ContainerClassLoader) parent;\n                if (TextUtils.equals(item.containerKey, containerKey)) {\n                    return item;\n                }\n            }\n            parent = parent.getParent();\n        }\n        return null;\n    }\n\n    private static void removeContainerClassLoader(ContainerClassLoader containerClassLoader) throws Exception {\n        ClassLoader pathClassLoader = MultiDynamicContainer.class.getClassLoader();\n        ClassLoader child = pathClassLoader;\n        ClassLoader parent = pathClassLoader.getParent();\n        while (parent != null) {\n            if (parent == containerClassLoader) {\n                break;\n            }\n            child = parent;\n            parent = parent.getParent();\n        }\n        if (child != null && parent == containerClassLoader) {\n            DynamicRuntime.hackParentClassLoader(child, containerClassLoader.getParent());\n        }\n    }\n\n    private static void hackContainerClassLoader(String containerKey, InstalledApk containerApk) throws Exception {\n        ClassLoader pathClassLoader = MultiDynamicContainer.class.getClassLoader();\n        ContainerClassLoader containerClassLoader = new ContainerClassLoader(containerKey, containerApk, pathClassLoader.getParent());\n        DynamicRuntime.hackParentClassLoader(pathClassLoader, containerClassLoader);\n    }\n\n    private static class ContainerClassLoader extends BaseDexClassLoader {\n        private String apkFilePath;\n        private String containerKey;\n\n        public ContainerClassLoader(String containerKey, InstalledApk installedApk, ClassLoader parent) {\n            super(installedApk.apkFilePath, installedApk.oDexPath != null ? new File(installedApk.oDexPath) : null, installedApk.libraryPath, parent);\n            this.containerKey = containerKey;\n            this.apkFilePath = installedApk.apkFilePath;\n        }\n\n        @Override\n        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n            Class<?> c = findLoadedClass(name);\n            if (c == null) {\n                // 对于当前apk中的类不进行双亲委派查找\n                // 因为这个ClassLoader中的类比较特殊，Activity等壳子接口的方法上存在当前环境可能不存在的类，\n                // 比如ContextParams这种API 31新引入的类。如果采用双亲委派，PluginContainerActivity等基类\n                // 可能跨ClassLoader加载，会触发HasSameSignatureWithDifferentClassLoaders的校验，\n                // 进而加载所有方法签名上的类型，从而可能在低版本机器上报找不到类的错误。\n                try {\n                    c = findClass(name);\n                } catch (ClassNotFoundException ignored) {\n                    c = super.loadClass(name, resolve);\n                }\n            }\n            return c;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/src/main/java/com/tencent/shadow/dynamic/host/MultiLoaderPluginProcessService.java",
    "content": "package com.tencent.shadow.dynamic.host;\n\nimport android.app.Application;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nimport java.io.File;\nimport java.util.HashMap;\n\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_FILE_NOT_FOUND_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RELOAD_LOADER_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RELOAD_RUNTIME_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RESET_UUID_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_RUNTIME_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.FailedException.ERROR_CODE_UUID_MANAGER_NULL_EXCEPTION;\n\npublic class MultiLoaderPluginProcessService extends BasePluginProcessService {\n\n    static final ActivityHolder sActivityHolder = new ActivityHolder();\n    private final MultiLoaderPpsBinder mPpsControllerBinder = new MultiLoaderPpsBinder(this);\n\n    private HashMap<String, String> mUuidMap = new HashMap<>();\n    private HashMap<String, UuidManager> mUuidManagerMap = new HashMap<>();\n    private HashMap<String, PluginLoaderImpl> mPluginLoaderMap = new HashMap<>();\n    private HashMap<String, Boolean> mRuntimeLoadedMap = new HashMap<>();\n\n    public static Application.ActivityLifecycleCallbacks getActivityHolder() {\n        return sActivityHolder;\n    }\n\n    public static MultiLoaderPpsController wrapBinder(IBinder ppsBinder) {\n        return new MultiLoaderPpsController(ppsBinder);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onBind:\" + this);\n        }\n        return mPpsControllerBinder;\n    }\n\n    synchronized void loadRuntimeForPlugin(String pluginKey, String uuid) throws FailedException {\n        String logIdentity = \"pluginKey=\" + pluginKey + \"|uuid=\" + uuid;\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadRuntimeForPlugin:\" + logIdentity);\n        }\n\n        UuidManager uuidManager = checkUuidManagerNotNull(pluginKey);\n        addUuidForPlugin(pluginKey, uuid);\n        if (isRuntimeLoaded(pluginKey)) {\n            throw new FailedException(ERROR_CODE_RELOAD_RUNTIME_EXCEPTION, \"重复调用loadRuntime,\" + logIdentity);\n        }\n        try {\n\n            InstalledApk installedApk;\n            try {\n                installedApk = uuidManager.getRuntime(uuid);\n            } catch (RemoteException e) {\n                Log.i(\"PluginProcessService\", \"uuidManager.getRuntime new FailedException\");\n                throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());\n            } catch (NotFoundException e) {\n                Log.i(\"PluginProcessService\", \"uuidManager.getRuntime new NotFoundException\");\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, \"pluginKey=\" + pluginKey + \", uuid=\" + uuid + \"的Runtime没有找到。cause:\" + e.getMessage());\n            }\n\n            InstalledApk installedRuntimeApk = new InstalledApk(installedApk.apkFilePath, installedApk.oDexPath, installedApk.libraryPath);\n            MultiDynamicContainer.loadContainerApk(pluginKey, installedRuntimeApk);\n            markRuntimeLoaded(pluginKey);\n        } catch (RuntimeException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadRuntimeForPlugin发生RuntimeException\", e);\n            }\n            throw new FailedException(e);\n        }\n    }\n\n    synchronized void loadPluginLoaderForPlugin(String pluginKey, String uuid) throws FailedException {\n        String logIdentity = \"pluginKey=\" + pluginKey + \"|uuid=\" + uuid;\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadPluginLoader:\" + logIdentity);\n        }\n        UuidManager uuidManager = checkUuidManagerNotNull(pluginKey);\n        addUuidForPlugin(pluginKey, uuid);\n        if (mPluginLoaderMap.get(pluginKey) != null) {\n            throw new FailedException(ERROR_CODE_RELOAD_LOADER_EXCEPTION, \"重复调用loadPluginLoader\");\n        }\n        try {\n            InstalledApk installedApk;\n            try {\n                installedApk = uuidManager.getPluginLoader(uuid);\n                if (mLogger.isInfoEnabled()) {\n                    mLogger.info(\"取出\" + logIdentity + \"的Loader apk:\" + installedApk.apkFilePath);\n                }\n            } catch (RemoteException e) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"获取Loader Apk失败\", e);\n                }\n                throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());\n            } catch (NotFoundException e) {\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, logIdentity + \"的PluginLoader没有找到。cause:\" + e.getMessage());\n            }\n            File file = new File(installedApk.apkFilePath);\n            if (!file.exists()) {\n                throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, file.getAbsolutePath() + \"文件不存在\");\n            }\n\n            PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());\n            pluginLoader.setUuidManager(uuidManager);\n            mPluginLoaderMap.put(pluginKey, pluginLoader);\n        } catch (RuntimeException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadPluginLoader发生RuntimeException\", e);\n            }\n            throw new FailedException(e);\n        } catch (FailedException e) {\n            throw e;\n        } catch (Exception e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"loadPluginLoader发生Exception\", e);\n            }\n            String msg = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();\n            throw new FailedException(ERROR_CODE_RUNTIME_EXCEPTION, \"加载动态实现失败 cause：\" + msg);\n        }\n    }\n\n    synchronized void setUuidManagerForPlugin(String pluginKey, UuidManager uuidManager) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"setUuidManagerForPlugin pluginKey=\" + pluginKey + \", uuidManager==\" + uuidManager);\n        }\n        mUuidManagerMap.put(pluginKey, uuidManager);\n        PluginLoaderImpl pluginLoader = mPluginLoaderMap.get(pluginKey);\n        if (pluginLoader != null) {\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"更新PluginLoader的uuidManager\");\n            }\n            pluginLoader.setUuidManager(uuidManager);\n        }\n    }\n\n    synchronized PpsStatus getPpsStatusForPlugin(String pluginKey) {\n        return new PpsStatus(mUuidMap.get(pluginKey), isRuntimeLoaded(pluginKey), mPluginLoaderMap.get(pluginKey) != null, mUuidManagerMap.get(pluginKey) != null);\n    }\n\n    synchronized IBinder getPluginLoaderForPlugin(String pluginKey) {\n        return mPluginLoaderMap.get(pluginKey);\n    }\n\n    void exit() {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"exit \");\n        }\n        MultiLoaderPluginProcessService.sActivityHolder.finishAll();\n        System.exit(0);\n        try {\n            wait();\n        } catch (InterruptedException ignored) {\n        }\n    }\n\n    private UuidManager checkUuidManagerNotNull(String pluginKey) throws FailedException {\n        UuidManager uuidManager = mUuidManagerMap.get(pluginKey);\n        if (uuidManager == null) {\n            throw new FailedException(ERROR_CODE_UUID_MANAGER_NULL_EXCEPTION, \"mUuidManager == null\");\n        }\n        return uuidManager;\n    }\n\n    private boolean isRuntimeLoaded(String pluginKey) {\n        Boolean result = mRuntimeLoadedMap.get(pluginKey);\n        return result != null && result;\n    }\n\n    private void markRuntimeLoaded(String pluginKey) {\n        mRuntimeLoadedMap.put(pluginKey, true);\n    }\n\n    private void addUuidForPlugin(String pluginKey, String uuid) throws FailedException {\n        String preUuid = mUuidMap.get(pluginKey);\n        if (preUuid != null && !TextUtils.equals(uuid, preUuid)) {\n            throw new FailedException(ERROR_CODE_RESET_UUID_EXCEPTION, \"Plugin=\" + pluginKey + \"已设置过uuid==\" + preUuid + \", 试图设置uuid==\" + uuid);\n        } else if (preUuid == null) {\n            mUuidMap.put(pluginKey, uuid);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/src/main/java/com/tencent/shadow/dynamic/host/MultiLoaderPpsBinder.java",
    "content": "package com.tencent.shadow.dynamic.host;\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\n\nimport static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;\n\npublic class MultiLoaderPpsBinder extends Binder {\n\n    static final String DESCRIPTOR = MultiLoaderPpsBinder.class.getName();\n\n    static final int TRANSACTION_CODE_NO_EXCEPTION = 0;\n    static final int TRANSACTION_CODE_FAILED_EXCEPTION = 1;\n\n    static final int TRANSACTION_loadRuntimeForPlugin = (FIRST_CALL_TRANSACTION);\n    static final int TRANSACTION_loadPluginLoaderForPlugin = (FIRST_CALL_TRANSACTION + 1);\n    static final int TRANSACTION_setUuidManagerForPlugin = (FIRST_CALL_TRANSACTION + 2);\n    static final int TRANSACTION_getPpsStatusForPlugin = (FIRST_CALL_TRANSACTION + 3);\n    static final int TRANSACTION_getPluginLoaderForPlugin = (FIRST_CALL_TRANSACTION + 4);\n    static final int TRANSACTION_exit = (FIRST_CALL_TRANSACTION + 5);\n\n    private final MultiLoaderPluginProcessService mPps;\n\n    MultiLoaderPpsBinder(MultiLoaderPluginProcessService pps) {\n        mPps = pps;\n    }\n\n    @Override\n    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {\n        switch (code) {\n            case INTERFACE_TRANSACTION: {\n                reply.writeString(DESCRIPTOR);\n                return true;\n            }\n            case TRANSACTION_loadRuntimeForPlugin: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0, _arg1;\n                _arg0 = data.readString();\n                _arg1 = data.readString();\n                try {\n                    mPps.loadRuntimeForPlugin(_arg0, _arg1);\n                    reply.writeInt(TRANSACTION_CODE_NO_EXCEPTION);\n                } catch (FailedException e) {\n                    reply.writeInt(TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case TRANSACTION_loadPluginLoaderForPlugin: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0, _arg1;\n                _arg0 = data.readString();\n                _arg1 = data.readString();\n                try {\n                    mPps.loadPluginLoaderForPlugin(_arg0, _arg1);\n                    reply.writeInt(TRANSACTION_CODE_NO_EXCEPTION);\n                } catch (FailedException e) {\n                    reply.writeInt(TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case TRANSACTION_setUuidManagerForPlugin: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0 = data.readString();\n                IBinder iBinder = data.readStrongBinder();\n                UuidManager uuidManager = iBinder != null ? new BinderUuidManager(iBinder) : null;\n                mPps.setUuidManagerForPlugin(_arg0, uuidManager);\n                reply.writeNoException();\n                return true;\n            }\n            case TRANSACTION_getPpsStatusForPlugin: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0 = data.readString();\n                PpsStatus ppsStatus = mPps.getPpsStatusForPlugin(_arg0);\n                reply.writeNoException();\n                ppsStatus.writeToParcel(reply, PARCELABLE_WRITE_RETURN_VALUE);\n                return true;\n            }\n            case TRANSACTION_getPluginLoaderForPlugin: {\n                data.enforceInterface(DESCRIPTOR);\n                String _arg0 = data.readString();\n                IBinder pluginLoader = mPps.getPluginLoaderForPlugin(_arg0);\n                reply.writeNoException();\n                reply.writeStrongBinder(pluginLoader);\n                return true;\n            }\n            case TRANSACTION_exit: {\n                data.enforceInterface(DESCRIPTOR);\n                mPps.exit();\n                reply.writeNoException();\n                return true;\n            }\n            default:\n                return false;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-host-multi-loader-ext/src/main/java/com/tencent/shadow/dynamic/host/MultiLoaderPpsController.java",
    "content": "package com.tencent.shadow.dynamic.host;\n\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.MultiLoaderPpsBinder;\nimport com.tencent.shadow.dynamic.host.PpsStatus;\n\nimport static com.tencent.shadow.dynamic.host.MultiLoaderPpsBinder.TRANSACTION_CODE_FAILED_EXCEPTION;\nimport static com.tencent.shadow.dynamic.host.MultiLoaderPpsBinder.TRANSACTION_CODE_NO_EXCEPTION;\n\npublic class MultiLoaderPpsController {\n    final private IBinder mRemote;\n\n    MultiLoaderPpsController(IBinder remote) {\n        mRemote = remote;\n    }\n\n    public void loadRuntimeForPlugin(String pluginKey, String uuid) throws RemoteException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            _data.writeString(pluginKey);\n            _data.writeString(uuid);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_loadRuntimeForPlugin, _data, _reply, 0);\n            int i = _reply.readInt();\n            if (i == TRANSACTION_CODE_FAILED_EXCEPTION) {\n                throw new FailedException(_reply);\n            } else if (i != TRANSACTION_CODE_NO_EXCEPTION) {\n                throw new RuntimeException(\"不认识的Code==\" + i);\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public void loadPluginLoaderForPlugin(String pluginKey, String uuid) throws RemoteException, FailedException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            _data.writeString(pluginKey);\n            _data.writeString(uuid);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_loadPluginLoaderForPlugin, _data, _reply, 0);\n            int i = _reply.readInt();\n            if (i == TRANSACTION_CODE_FAILED_EXCEPTION) {\n                throw new FailedException(_reply);\n            } else if (i != TRANSACTION_CODE_NO_EXCEPTION) {\n                throw new RuntimeException(\"不认识的Code==\" + i);\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public void setUuidManagerForPlugin(String pluginKey, IBinder uuidManagerBinder) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            _data.writeString(pluginKey);\n            _data.writeStrongBinder(uuidManagerBinder);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_setUuidManagerForPlugin, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    public PpsStatus getPpsStatusForPlugin(String pluginKey) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        PpsStatus _result;\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            _data.writeString(pluginKey);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_getPpsStatusForPlugin, _data, _reply, 0);\n            _reply.readException();\n            _result = new PpsStatus(_reply);\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    public IBinder getPluginLoaderForPlugin(String pluginKey) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        IBinder _result;\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            _data.writeString(pluginKey);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_getPluginLoaderForPlugin, _data, _reply, 0);\n            _reply.readException();\n            _result = _reply.readStrongBinder();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    public void exit() throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(MultiLoaderPpsBinder.DESCRIPTOR);\n            mRemote.transact(MultiLoaderPpsBinder.TRANSACTION_exit, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader/src/main/java/com/tencent/shadow/dynamic/loader/PluginLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n/*\n * This file is auto-generated.  DO NOT MODIFY.\n * Original file: /Users/shifujun/Codes/Android/shadow/projects/sdk/dynamic/dynamic-loader/dynamic-loader-aar/src/main/aidl/com/tencent/shadow/dynamic/loader/PluginLoader.aidl\n */\npackage com.tencent.shadow.dynamic.loader;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\nimport java.util.Map;\n\npublic interface PluginLoader {\n    String DESCRIPTOR = PluginLoader.class.getName();\n    int TRANSACTION_loadPlugin = (IBinder.FIRST_CALL_TRANSACTION);\n    int TRANSACTION_getLoadedPlugin = (IBinder.FIRST_CALL_TRANSACTION + 1);\n    int TRANSACTION_callApplicationOnCreate = (IBinder.FIRST_CALL_TRANSACTION + 2);\n    int TRANSACTION_convertActivityIntent = (IBinder.FIRST_CALL_TRANSACTION + 3);\n    int TRANSACTION_startPluginService = (IBinder.FIRST_CALL_TRANSACTION + 4);\n    int TRANSACTION_stopPluginService = (IBinder.FIRST_CALL_TRANSACTION + 5);\n    int TRANSACTION_bindPluginService = (IBinder.FIRST_CALL_TRANSACTION + 6);\n    int TRANSACTION_unbindService = (IBinder.FIRST_CALL_TRANSACTION + 7);\n    int TRANSACTION_startActivityInPluginProcess = (IBinder.FIRST_CALL_TRANSACTION + 8);\n\n\n    void loadPlugin(String partKey) throws RemoteException;\n\n    Map getLoadedPlugin() throws RemoteException;\n\n    void callApplicationOnCreate(String partKey) throws RemoteException;\n\n    Intent convertActivityIntent(Intent pluginActivityIntent) throws RemoteException;\n\n    ComponentName startPluginService(Intent pluginServiceIntent) throws RemoteException;\n\n    boolean stopPluginService(Intent pluginServiceIntent) throws RemoteException;\n\n    boolean bindPluginService(Intent pluginServiceIntent, PluginServiceConnection connection, int flags) throws RemoteException;\n\n    void unbindService(PluginServiceConnection conn) throws RemoteException;\n\n    void startActivityInPluginProcess(Intent intent) throws RemoteException;\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader/src/main/java/com/tencent/shadow/dynamic/loader/PluginServiceConnection.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n/*\n * This file is auto-generated.  DO NOT MODIFY.\n * Original file: /Users/shifujun/Codes/Android/shadow/projects/sdk/dynamic/dynamic-loader/dynamic-loader-aar/src/main/aidl/com/tencent/shadow/dynamic/loader/IServiceConnection.aidl\n */\npackage com.tencent.shadow.dynamic.loader;\n\nimport android.content.ComponentName;\nimport android.os.IBinder;\n\npublic interface PluginServiceConnection {\n    String DESCRIPTOR = PluginServiceConnection.class.getName();\n    int TRANSACTION_onServiceConnected = IBinder.FIRST_CALL_TRANSACTION;\n    int TRANSACTION_onServiceDisconnected = IBinder.FIRST_CALL_TRANSACTION + 1;\n\n    void onServiceConnected(ComponentName name, IBinder service);\n\n    void onServiceDisconnected(ComponentName name);\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/build.gradle",
    "content": "import com.tencent.shadow.coding.common_jar_settings.AndroidJar\n\napply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\napply plugin: 'kotlin'\n\ncompileKotlin {\n    sourceCompatibility = JavaVersion.VERSION_1_7\n    targetCompatibility = JavaVersion.VERSION_1_7\n\n    kotlinOptions {\n        jvmTarget = \"1.6\"\n        noJdk = true\n        noStdlib = true\n    }\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation 'com.tencent.shadow.core:loader'\n    compileOnly 'com.tencent.shadow.core:activity-container'\n    compileOnly 'com.tencent.shadow.core:common'\n    compileOnly project(':dynamic-host')\n    compileOnly project(':dynamic-loader')\n    compileOnly files(AndroidJar.ANDROID_JAR_PATH)\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/impl/LoaderFactoryImpl.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl\n\n@Deprecated(\"兼容旧版本dynamic-host访问这个类名\", level = DeprecationLevel.HIDDEN)\nclass LoaderFactoryImpl : com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl() {\n}"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/loader/impl/BinderPluginServiceConnection.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl\n\nimport android.content.ComponentName\nimport android.os.IBinder\nimport android.os.Parcel\nimport android.os.RemoteException\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection\n\nclass BinderPluginServiceConnection internal constructor(internal val mRemote: IBinder) {\n\n    @Throws(RemoteException::class)\n    fun onServiceConnected(name: ComponentName?, service: IBinder) {\n        val _data = Parcel.obtain()\n        val _reply = Parcel.obtain()\n        try {\n            _data.writeInterfaceToken(PluginServiceConnection.DESCRIPTOR)\n            if (name != null) {\n                _data.writeInt(1)\n                name.writeToParcel(_data, 0)\n            } else {\n                _data.writeInt(0)\n            }\n            _data.writeStrongBinder(service)\n            mRemote.transact(\n                PluginServiceConnection.TRANSACTION_onServiceConnected,\n                _data,\n                _reply,\n                0\n            )\n            _reply.readException()\n        } finally {\n            _reply.recycle()\n            _data.recycle()\n        }\n    }\n\n    @Throws(RemoteException::class)\n    fun onServiceDisconnected(name: ComponentName?) {\n        val _data = Parcel.obtain()\n        val _reply = Parcel.obtain()\n        try {\n            _data.writeInterfaceToken(PluginServiceConnection.DESCRIPTOR)\n            if (name != null) {\n                _data.writeInt(1)\n                name.writeToParcel(_data, 0)\n            } else {\n                _data.writeInt(0)\n            }\n            mRemote.transact(\n                PluginServiceConnection.TRANSACTION_onServiceDisconnected,\n                _data,\n                _reply,\n                0\n            )\n            _reply.readException()\n        } finally {\n            _reply.recycle()\n            _data.recycle()\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/loader/impl/CoreLoaderFactory.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl\n\nimport android.content.Context\nimport com.tencent.shadow.core.loader.ShadowPluginLoader\n\ninterface CoreLoaderFactory {\n    fun build(hostAppContext: Context): ShadowPluginLoader\n}"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/loader/impl/DynamicPluginLoader.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.os.Handler\nimport android.os.IBinder\nimport android.os.Looper\nimport com.tencent.shadow.core.loader.ShadowPluginLoader\nimport com.tencent.shadow.core.runtime.container.ContentProviderDelegateProviderHolder\nimport com.tencent.shadow.core.runtime.container.DelegateProviderHolder\nimport com.tencent.shadow.dynamic.host.UuidManager\nimport java.util.concurrent.CountDownLatch\n\ninternal class DynamicPluginLoader(hostContext: Context, uuid: String) {\n    companion object {\n        private const val CORE_LOADER_FACTORY_IMPL_NAME =\n            \"com.tencent.shadow.dynamic.loader.impl.CoreLoaderFactoryImpl\"\n    }\n\n    fun setUuidManager(p0: UuidManager?) {\n        if (p0 != null)\n            mUuidManager = p0\n        //todo #30 兼容mUuidManager为null时的逻辑\n    }\n\n    private val mPluginLoader: ShadowPluginLoader\n\n    private val mDynamicLoaderClassLoader: ClassLoader =\n        DynamicPluginLoader::class.java.classLoader!!\n\n    private var mContext: Context\n\n    private lateinit var mUuidManager: UuidManager\n\n    private var mUuid: String\n\n    private val mUiHandler = Handler(Looper.getMainLooper())\n\n    /**\n     * 同一个IServiceConnection只会对应一个ServiceConnection对象，此Map就是保存这种对应关系\n     */\n    private val mConnectionMap = HashMap<IBinder, ServiceConnection>()\n\n    init {\n        try {\n            val coreLoaderFactory = mDynamicLoaderClassLoader.getInterface(\n                CoreLoaderFactory::class.java,\n                CORE_LOADER_FACTORY_IMPL_NAME\n            )\n            mPluginLoader = coreLoaderFactory.build(hostContext)\n            DelegateProviderHolder.setDelegateProvider(\n                mPluginLoader.delegateProviderKey,\n                mPluginLoader\n            )\n            ContentProviderDelegateProviderHolder.setContentProviderDelegateProvider(mPluginLoader)\n            mPluginLoader.onCreate()\n        } catch (e: Exception) {\n            throw RuntimeException(\"当前的classLoader找不到PluginLoader的实现\", e)\n        }\n        mContext = hostContext\n        mUuid = uuid\n    }\n\n    fun loadPlugin(partKey: String) {\n        val installedApk = mUuidManager.getPlugin(mUuid, partKey)\n        val future = mPluginLoader.loadPlugin(installedApk)\n        future.get()\n    }\n\n    fun getLoadedPlugin(): MutableMap<String, Boolean> {\n        val plugins = mPluginLoader.getAllPluginPart()\n        val loadPlugins = hashMapOf<String, Boolean>()\n        for (part in plugins) {\n            loadPlugins[part.key] = part.value.application.isCallOnCreate\n        }\n        return loadPlugins\n    }\n\n    @Synchronized\n    fun callApplicationOnCreate(partKey: String) {\n        mPluginLoader.callApplicationOnCreate(partKey)\n    }\n\n    fun convertActivityIntent(pluginActivityIntent: Intent): Intent? {\n        return mPluginLoader.mComponentManager.convertPluginActivityIntent(pluginActivityIntent)\n    }\n\n    @Synchronized\n    fun startPluginService(pluginServiceIntent: Intent): ComponentName? {\n\n        fun realAction(): ComponentName? {\n            return mPluginLoader.getPluginServiceManager().startPluginService(pluginServiceIntent)\n        }\n\n\n        // 确保在ui线程调用\n        var componentName: ComponentName? = null\n        if (isUiThread()) {\n            componentName = realAction()\n        } else {\n            val waitUiLock = CountDownLatch(1)\n            mUiHandler.post {\n                componentName = realAction()\n                waitUiLock.countDown()\n            }\n            waitUiLock.await()\n        }\n\n        return componentName\n    }\n\n    @Synchronized\n    fun stopPluginService(pluginServiceIntent: Intent): Boolean {\n\n        fun realAction(): Boolean {\n            return mPluginLoader.getPluginServiceManager().stopPluginService(pluginServiceIntent)\n        }\n\n        // 确保在ui线程调用\n        var stopped: Boolean = false\n        if (isUiThread()) {\n            stopped = realAction()\n        } else {\n            val waitUiLock = CountDownLatch(1)\n            mUiHandler.post {\n                stopped = realAction()\n                waitUiLock.countDown()\n            }\n            waitUiLock.await()\n        }\n        return stopped\n    }\n\n    @Synchronized\n    fun bindPluginService(\n        pluginServiceIntent: Intent,\n        binderPsc: BinderPluginServiceConnection,\n        flags: Int\n    ): Boolean {\n\n        fun realAction(): Boolean {\n            if (mConnectionMap[binderPsc.mRemote] == null) {\n                mConnectionMap[binderPsc.mRemote] = ServiceConnectionWrapper(binderPsc)\n            }\n\n            val connWrapper = mConnectionMap[binderPsc.mRemote]!!\n            return mPluginLoader.getPluginServiceManager()\n                .bindPluginService(pluginServiceIntent, connWrapper, flags)\n        }\n        // 确保在ui线程调用\n        var stop: Boolean = false\n        if (isUiThread()) {\n            stop = realAction()\n        } else {\n            val waitUiLock = CountDownLatch(1)\n            mUiHandler.post {\n                stop = realAction()\n                waitUiLock.countDown()\n            }\n            waitUiLock.await();\n        }\n\n        return stop\n\n    }\n\n    @Synchronized\n    fun unbindService(connBinder: IBinder) {\n        mUiHandler.post {\n            mConnectionMap[connBinder]?.let {\n                mConnectionMap.remove(connBinder)\n                mPluginLoader.getPluginServiceManager().unbindPluginService(it)\n            }\n        }\n    }\n\n    @Synchronized\n    fun startActivityInPluginProcess(intent: Intent) {\n        mUiHandler.post {\n            mContext.startActivity(intent)\n        }\n    }\n\n    private class ServiceConnectionWrapper(private val mConnection: BinderPluginServiceConnection) :\n        ServiceConnection {\n\n        override fun onServiceDisconnected(name: ComponentName) {\n            mConnection.onServiceDisconnected(name)\n        }\n\n        override fun onServiceConnected(name: ComponentName, service: IBinder) {\n            mConnection.onServiceConnected(name, service)\n        }\n\n    }\n\n    private fun isUiThread(): Boolean {\n\n        return Thread.currentThread() === Looper.getMainLooper().thread\n    }\n\n    /**\n     * 从apk中读取接口的实现\n     *\n     * @param clazz     接口类\n     * @param className 实现类的类名\n     * @param <T>       接口类型\n     * @return 所需接口\n     * @throws Exception\n    </T> */\n    @Throws(Exception::class)\n    fun <T> ClassLoader.getInterface(clazz: Class<T>, className: String): T {\n        try {\n            val interfaceImplementClass = loadClass(className)\n            val interfaceImplement = interfaceImplementClass.newInstance()\n            return clazz.cast(interfaceImplement) as T\n        } catch (e: ClassNotFoundException) {\n            throw Exception(e)\n        } catch (e: InstantiationException) {\n            throw Exception(e)\n        } catch (e: ClassCastException) {\n            throw Exception(e)\n        } catch (e: IllegalAccessException) {\n            throw Exception(e)\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/loader/impl/LoaderFactoryImpl.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl\n\nimport android.content.Context\nimport com.tencent.shadow.dynamic.host.LoaderFactory\nimport com.tencent.shadow.dynamic.host.PluginLoaderImpl\n\nopen class LoaderFactoryImpl : LoaderFactory {\n    override fun buildLoader(p0: String, p2: Context): PluginLoaderImpl {\n        return PluginLoaderBinder(DynamicPluginLoader(p2, p0))\n    }\n}"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-loader-impl/src/main/kotlin/com/tencent/shadow/dynamic/loader/impl/PluginLoaderBinder.kt",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl\n\nimport android.content.Intent\nimport android.os.IBinder\nimport com.tencent.shadow.dynamic.host.PluginLoaderImpl\nimport com.tencent.shadow.dynamic.host.UuidManager\nimport com.tencent.shadow.dynamic.loader.PluginLoader\n\ninternal class PluginLoaderBinder(private val mDynamicPluginLoader: DynamicPluginLoader) :\n    android.os.Binder(), PluginLoaderImpl {\n    override fun setUuidManager(uuidManager: UuidManager?) {\n        mDynamicPluginLoader.setUuidManager(uuidManager)\n    }\n\n    @Throws(android.os.RemoteException::class)\n    public override fun onTransact(\n        code: Int,\n        data: android.os.Parcel,\n        reply: android.os.Parcel?,\n        flags: Int\n    ): Boolean {\n        if (reply == null) {\n            throw NullPointerException(\"reply == null\")\n        }\n        when (code) {\n            IBinder.INTERFACE_TRANSACTION -> {\n                reply.writeString(PluginLoader.DESCRIPTOR)\n                return true\n            }\n            PluginLoader.TRANSACTION_loadPlugin -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val _arg0: String\n                _arg0 = data.readString()!!\n                try {\n                    mDynamicPluginLoader.loadPlugin(_arg0)\n                    reply.writeNoException()\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_getLoadedPlugin -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                try {\n                    val _result = mDynamicPluginLoader.getLoadedPlugin()\n                    reply.writeNoException()\n                    reply.writeMap(_result as Map<*, *>?)\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n\n                return true\n            }\n            PluginLoader.TRANSACTION_callApplicationOnCreate -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val _arg0: String\n                _arg0 = data.readString()!!\n                try {\n                    mDynamicPluginLoader.callApplicationOnCreate(_arg0)\n                    reply.writeNoException()\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n\n                return true\n            }\n            PluginLoader.TRANSACTION_convertActivityIntent -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val intent = if (0 != data.readInt()) {\n                    Intent.CREATOR.createFromParcel(data)\n                } else {\n                    reply.writeException(NullPointerException(\"intent==null\"))\n                    return true\n                }\n\n                try {\n                    val _result =\n                        mDynamicPluginLoader.convertActivityIntent(intent)\n                    reply.writeNoException()\n                    if (_result != null) {\n                        reply.writeInt(1)\n                        _result.writeToParcel(\n                            reply,\n                            android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE\n                        )\n                    } else {\n                        reply.writeInt(0)\n                    }\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_startPluginService -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val intent = if (0 != data.readInt()) {\n                    Intent.CREATOR.createFromParcel(data)\n                } else {\n                    reply.writeException(NullPointerException(\"intent==null\"))\n                    return true\n                }\n\n                try {\n                    val _result = mDynamicPluginLoader.startPluginService(intent)\n                    reply.writeNoException()\n                    if (_result != null) {\n                        reply.writeInt(1)\n                        _result.writeToParcel(\n                            reply,\n                            android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE\n                        )\n                    } else {\n                        reply.writeInt(0)\n                    }\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_stopPluginService -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val intent = if (0 != data.readInt()) {\n                    Intent.CREATOR.createFromParcel(data)\n                } else {\n                    reply.writeException(NullPointerException(\"intent==null\"))\n                    return true\n                }\n                try {\n                    val _result = mDynamicPluginLoader.stopPluginService(intent)\n                    reply.writeNoException()\n                    reply.writeInt(if (_result) 1 else 0)\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_bindPluginService -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                val intent = if (0 != data.readInt()) {\n                    Intent.CREATOR.createFromParcel(data)\n                } else {\n                    reply.writeException(NullPointerException(\"intent==null\"))\n                    return true\n                }\n                val _arg1 = BinderPluginServiceConnection(data.readStrongBinder())\n                val _arg2: Int\n                _arg2 = data.readInt()\n                try {\n                    val _result = mDynamicPluginLoader.bindPluginService(\n                        intent,\n                        _arg1,\n                        _arg2\n                    )\n                    reply.writeNoException()\n                    reply.writeInt(if (_result) 1 else 0)\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_unbindService -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                try {\n                    mDynamicPluginLoader.unbindService(data.readStrongBinder())\n                    reply.writeNoException()\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n            PluginLoader.TRANSACTION_startActivityInPluginProcess -> {\n                data.enforceInterface(PluginLoader.DESCRIPTOR)\n                try {\n                    mDynamicPluginLoader.startActivityInPluginProcess(\n                        Intent.CREATOR.createFromParcel(\n                            data\n                        )\n                    )\n                    reply.writeNoException()\n                } catch (e: Exception) {\n                    reply.writeException(wrapExceptionForBinder(e))\n                }\n                return true\n            }\n        }\n        return super.onTransact(code, data, reply, flags)\n    }\n\n    /**\n     * Binder的内置writeException方法只支持特定几种Exception\n     * https://developer.android.com/reference/android/os/Parcel.html#writeException(java.lang.Exception)\n     */\n    private fun wrapExceptionForBinder(e: Exception): Exception {\n        return IllegalStateException(e.message, e.cause)\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/.gitignore",
    "content": "/build\n*.iml"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    compileOnly 'com.tencent.shadow.core:common'\n    implementation 'com.tencent.shadow.core:manager'\n    implementation project(':dynamic-loader')\n    compileOnly project(':dynamic-host')\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/cubershi/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/BaseDynamicPluginManager.java",
    "content": "package com.tencent.shadow.dynamic.manager;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.load_parameters.LoadParameters;\nimport com.tencent.shadow.core.manager.BasePluginManager;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.core.manager.installplugin.InstalledType;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.NotFoundException;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static android.content.Context.BIND_AUTO_CREATE;\n\nabstract public class BaseDynamicPluginManager extends BasePluginManager implements UuidManagerImpl {\n    private static final Logger mLogger = LoggerFactory.getLogger(BaseDynamicPluginManager.class);\n\n    public BaseDynamicPluginManager(Context context) {\n        super(context);\n    }\n\n    /**\n     * 防止绑定service重入\n     */\n    private AtomicBoolean mServiceConnecting = new AtomicBoolean(false);\n    /**\n     * 等待service绑定完成的计数器\n     */\n    private AtomicReference<CountDownLatch> mConnectCountDownLatch = new AtomicReference<>();\n\n    /**\n     * 启动PluginProcessService\n     *\n     * @param serviceName 注册在宿主中的插件进程管理service完整名字\n     */\n    public final void bindPluginProcessService(final String serviceName) {\n        if (mServiceConnecting.get()) {\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"pps service connecting\");\n            }\n            return;\n        }\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"bindPluginProcessService \" + serviceName);\n        }\n\n        mConnectCountDownLatch.set(new CountDownLatch(1));\n\n        mServiceConnecting.set(true);\n\n        final CountDownLatch startBindingLatch = new CountDownLatch(1);\n        final boolean[] asyncResult = new boolean[1];\n        mUiHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                Intent intent = new Intent();\n                intent.setComponent(new ComponentName(mHostContext, serviceName));\n                boolean binding = mHostContext.bindService(intent, new ServiceConnection() {\n                    @Override\n                    public void onServiceConnected(ComponentName name, IBinder service) {\n                        if (mLogger.isInfoEnabled()) {\n                            mLogger.info(\"onServiceConnected connectCountDownLatch:\" + mConnectCountDownLatch);\n                        }\n                        mServiceConnecting.set(false);\n\n                        // service connect 后处理逻辑\n                        onPluginServiceConnected(name, service);\n\n                        mConnectCountDownLatch.get().countDown();\n\n                        if (mLogger.isInfoEnabled()) {\n                            mLogger.info(\"onServiceConnected countDown:\" + mConnectCountDownLatch);\n                        }\n                    }\n\n                    @Override\n                    public void onServiceDisconnected(ComponentName name) {\n                        if (mLogger.isInfoEnabled()) {\n                            mLogger.info(\"onServiceDisconnected\");\n                        }\n                        mServiceConnecting.set(false);\n                        onPluginServiceDisconnected(name);\n                    }\n                }, BIND_AUTO_CREATE);\n                asyncResult[0] = binding;\n                startBindingLatch.countDown();\n            }\n        });\n        try {\n            //等待bindService真正开始\n            startBindingLatch.await(10, TimeUnit.SECONDS);\n            if (!asyncResult[0]) {\n                throw new IllegalArgumentException(\"无法绑定PPS:\" + serviceName);\n            }\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public final void waitServiceConnected(int timeout, TimeUnit timeUnit) throws TimeoutException {\n        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {\n            throw new RuntimeException(\"waitServiceConnected 不能在主线程中调用\");\n        }\n        try {\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"waiting service connect connectCountDownLatch:\" + mConnectCountDownLatch);\n            }\n            long s = System.currentTimeMillis();\n            boolean isTimeout = !mConnectCountDownLatch.get().await(timeout, timeUnit);\n            if (isTimeout) {\n                throw new TimeoutException(\"连接Service超时 ,等待了：\" + (System.currentTimeMillis() - s));\n            }\n            if (mLogger.isInfoEnabled()) {\n                mLogger.info(\"service connected \" + (System.currentTimeMillis() - s));\n            }\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    protected abstract void onPluginServiceConnected(ComponentName name, IBinder service);\n\n    protected abstract void onPluginServiceDisconnected(ComponentName name);\n\n    /**\n     * PluginManager对象创建的时候回调\n     *\n     * @param bundle 当PluginManager有更新时会回调老的PluginManager对象onSaveInstanceState存储数据，bundle不为null说明发生了更新\n     *               为null说明是首次创建\n     */\n    public void onCreate(Bundle bundle) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onCreate bundle:\" + bundle);\n        }\n    }\n\n    /**\n     * 当PluginManager有更新时会先回调老的PluginManager对象 onSaveInstanceState存储数据\n     *\n     * @param bundle 要存储的数据\n     */\n    public void onSaveInstanceState(Bundle bundle) {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onSaveInstanceState:\" + bundle);\n        }\n    }\n\n    /**\n     * 当PluginManager有更新时先会销毁老的PluginManager对象，回调对应的onDestroy\n     */\n    public void onDestroy() {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"onDestroy:\");\n        }\n    }\n\n    public InstalledApk getPlugin(String uuid, String partKey) throws FailedException, NotFoundException {\n        try {\n            InstalledPlugin.Part part;\n            try {\n                part = getPluginPartByPartKey(uuid, partKey);\n            } catch (RuntimeException e) {\n                throw new NotFoundException(\"uuid==\" + uuid + \"partKey==\" + partKey + \"的Plugin找不到\");\n            }\n            String businessName = part instanceof InstalledPlugin.PluginPart ? ((InstalledPlugin.PluginPart) part).businessName : null;\n            String[] dependsOn = part instanceof InstalledPlugin.PluginPart ? ((InstalledPlugin.PluginPart) part).dependsOn : null;\n            String[] hostWhiteList = part instanceof InstalledPlugin.PluginPart ? ((InstalledPlugin.PluginPart) part).hostWhiteList : null;\n            LoadParameters loadParameters\n                    = new LoadParameters(businessName, partKey, dependsOn, hostWhiteList);\n\n            Parcel parcelExtras = Parcel.obtain();\n            loadParameters.writeToParcel(parcelExtras, 0);\n            byte[] parcelBytes = parcelExtras.marshall();\n            parcelExtras.recycle();\n\n            return new InstalledApk(\n                    part.pluginFile.getAbsolutePath(),\n                    part.oDexDir == null ? null : part.oDexDir.getAbsolutePath(),\n                    part.libraryDir == null ? null : part.libraryDir.getAbsolutePath(),\n                    parcelBytes\n            );\n        } catch (RuntimeException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"getPlugin exception:\", e);\n            }\n            throw new FailedException(e);\n        }\n    }\n\n    private InstalledApk getInstalledPL(String uuid, int type) throws FailedException, NotFoundException {\n        try {\n            InstalledPlugin.Part part;\n            try {\n                part = getLoaderOrRunTimePart(uuid, type);\n            } catch (RuntimeException e) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"getInstalledPL exception:\", e);\n                }\n                throw new NotFoundException(\"uuid==\" + uuid + \" type==\" + type + \"没找到。cause：\" + e.getMessage());\n            }\n            return new InstalledApk(part.pluginFile.getAbsolutePath(),\n                    part.oDexDir == null ? null : part.oDexDir.getAbsolutePath(),\n                    part.libraryDir == null ? null : part.libraryDir.getAbsolutePath());\n        } catch (RuntimeException e) {\n            throw new FailedException(e);\n        }\n    }\n\n    public InstalledApk getPluginLoader(String uuid) throws FailedException, NotFoundException {\n        return getInstalledPL(uuid, InstalledType.TYPE_PLUGIN_LOADER);\n    }\n\n    public InstalledApk getRuntime(String uuid) throws FailedException, NotFoundException {\n        return getInstalledPL(uuid, InstalledType.TYPE_PLUGIN_RUNTIME);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/BinderPluginLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.manager;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.dynamic.loader.PluginLoader;\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nclass BinderPluginLoader implements PluginLoader {\n    final private IBinder mRemote;\n    final private ConcurrentHashMap<PluginServiceConnection, PluginServiceConnectionBinder> conMap =\n            new ConcurrentHashMap<>();\n\n    BinderPluginLoader(IBinder remote) {\n        mRemote = remote;\n    }\n\n    @Override\n    public void loadPlugin(String partKey) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            _data.writeString(partKey);\n            mRemote.transact(TRANSACTION_loadPlugin, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    @Override\n    public Map getLoadedPlugin() throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        Map _result;\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            mRemote.transact(TRANSACTION_getLoadedPlugin, _data, _reply, 0);\n            _reply.readException();\n            ClassLoader cl = (ClassLoader) this.getClass().getClassLoader();\n            _result = _reply.readHashMap(cl);\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public void callApplicationOnCreate(String partKey) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            _data.writeString(partKey);\n            mRemote.transact(TRANSACTION_callApplicationOnCreate, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    @Override\n    public Intent convertActivityIntent(Intent pluginActivityIntent) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        Intent _result;\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            if ((pluginActivityIntent != null)) {\n                _data.writeInt(1);\n                pluginActivityIntent.writeToParcel(_data, 0);\n            } else {\n                _data.writeInt(0);\n            }\n            mRemote.transact(TRANSACTION_convertActivityIntent, _data, _reply, 0);\n            _reply.readException();\n            if ((0 != _reply.readInt())) {\n                _result = Intent.CREATOR.createFromParcel(_reply);\n            } else {\n                _result = null;\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public ComponentName startPluginService(Intent pluginServiceIntent) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        ComponentName _result;\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            if ((pluginServiceIntent != null)) {\n                _data.writeInt(1);\n                pluginServiceIntent.writeToParcel(_data, 0);\n            } else {\n                _data.writeInt(0);\n            }\n            mRemote.transact(TRANSACTION_startPluginService, _data, _reply, 0);\n            _reply.readException();\n            if ((0 != _reply.readInt())) {\n                _result = ComponentName.CREATOR.createFromParcel(_reply);\n            } else {\n                _result = null;\n            }\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public boolean stopPluginService(Intent pluginServiceIntent) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        boolean _result;\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            if ((pluginServiceIntent != null)) {\n                _data.writeInt(1);\n                pluginServiceIntent.writeToParcel(_data, 0);\n            } else {\n                _data.writeInt(0);\n            }\n            mRemote.transact(TRANSACTION_stopPluginService, _data, _reply, 0);\n            _reply.readException();\n            _result = (0 != _reply.readInt());\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public boolean bindPluginService(Intent pluginServiceIntent, PluginServiceConnection connection, int flags) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        boolean _result;\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            if ((pluginServiceIntent != null)) {\n                _data.writeInt(1);\n                pluginServiceIntent.writeToParcel(_data, 0);\n            } else {\n                _data.writeInt(0);\n            }\n            PluginServiceConnectionBinder binder = null;\n            if (connection != null) {\n                binder = new PluginServiceConnectionBinder(connection);\n                conMap.put(connection, binder);\n            }\n            _data.writeStrongBinder(binder);\n            _data.writeInt(flags);\n            mRemote.transact(TRANSACTION_bindPluginService, _data, _reply, 0);\n            _reply.readException();\n            _result = (0 != _reply.readInt());\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n        return _result;\n    }\n\n    @Override\n    public void unbindService(PluginServiceConnection conn) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            PluginServiceConnectionBinder binder = null;\n            if (conn != null) {\n                binder = conMap.get(conn);\n                conMap.remove(conn);\n            }\n            _data.writeStrongBinder(binder);\n            mRemote.transact(TRANSACTION_unbindService, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n\n    @Override\n    public void startActivityInPluginProcess(Intent intent) throws RemoteException {\n        Parcel _data = Parcel.obtain();\n        Parcel _reply = Parcel.obtain();\n        try {\n            _data.writeInterfaceToken(DESCRIPTOR);\n            intent.writeToParcel(_data, 0);\n            mRemote.transact(TRANSACTION_startActivityInPluginProcess, _data, _reply, 0);\n            _reply.readException();\n        } finally {\n            _reply.recycle();\n            _data.recycle();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/PluginManagerThatUseDynamicLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.manager;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.os.DeadObjectException;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\nimport com.tencent.shadow.dynamic.host.PpsController;\nimport com.tencent.shadow.dynamic.host.PpsStatus;\nimport com.tencent.shadow.dynamic.loader.PluginLoader;\n\npublic abstract class PluginManagerThatUseDynamicLoader extends BaseDynamicPluginManager implements PluginManagerImpl {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(PluginManagerThatUseDynamicLoader.class);\n    /**\n     * 插件进程PluginProcessService的接口\n     */\n    protected PpsController mPpsController;\n\n    /**\n     * 插件加载服务端接口6\n     */\n    protected PluginLoader mPluginLoader;\n\n    protected PluginManagerThatUseDynamicLoader(Context context) {\n        super(context);\n    }\n\n    @Override\n    protected void onPluginServiceConnected(ComponentName name, IBinder service) {\n        mPpsController = PluginProcessService.wrapBinder(service);\n        try {\n            mPpsController.setUuidManager(new UuidManagerBinder(PluginManagerThatUseDynamicLoader.this));\n        } catch (DeadObjectException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"onServiceConnected RemoteException:\" + e);\n            }\n        } catch (RemoteException e) {\n            if (e.getClass().getSimpleName().equals(\"TransactionTooLargeException\")) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"onServiceConnected TransactionTooLargeException:\" + e);\n                }\n            } else {\n                throw new RuntimeException(e);\n            }\n        }\n\n        try {\n            IBinder iBinder = mPpsController.getPluginLoader();\n            if (iBinder != null) {\n                mPluginLoader = new BinderPluginLoader(iBinder);\n            }\n        } catch (RemoteException ignored) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"onServiceConnected mPpsController getPluginLoader:\", ignored);\n            }\n        }\n    }\n\n    @Override\n    protected void onPluginServiceDisconnected(ComponentName name) {\n        mPpsController = null;\n        mPluginLoader = null;\n    }\n\n    public final void loadRunTime(String uuid) throws RemoteException, FailedException {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadRunTime mPpsController:\" + mPpsController);\n        }\n        PpsStatus ppsStatus = mPpsController.getPpsStatus();\n        if (!ppsStatus.runtimeLoaded) {\n            mPpsController.loadRuntime(uuid);\n        }\n    }\n\n    public final void loadPluginLoader(String uuid) throws RemoteException, FailedException {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadPluginLoader mPluginLoader:\" + mPluginLoader);\n        }\n        if (mPluginLoader == null) {\n            PpsStatus ppsStatus = mPpsController.getPpsStatus();\n            if (!ppsStatus.loaderLoaded) {\n                mPpsController.loadPluginLoader(uuid);\n            }\n            IBinder iBinder = mPpsController.getPluginLoader();\n            mPluginLoader = new BinderPluginLoader(iBinder);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/PluginServiceConnectionBinder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.manager;\n\nimport android.content.ComponentName;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection;\n\nimport static com.tencent.shadow.dynamic.loader.PluginServiceConnection.DESCRIPTOR;\nimport static com.tencent.shadow.dynamic.loader.PluginServiceConnection.TRANSACTION_onServiceConnected;\nimport static com.tencent.shadow.dynamic.loader.PluginServiceConnection.TRANSACTION_onServiceDisconnected;\n\n/**\n * Local-side IPC implementation stub class.\n */\nclass PluginServiceConnectionBinder extends Binder {\n\n    private final PluginServiceConnection mPsc;\n\n    /**\n     * Construct the stub at attach it to the interface.\n     */\n    PluginServiceConnectionBinder(PluginServiceConnection psc) {\n        mPsc = psc;\n    }\n\n    @Override\n    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {\n        switch (code) {\n            case INTERFACE_TRANSACTION: {\n                reply.writeString(DESCRIPTOR);\n                return true;\n            }\n            case TRANSACTION_onServiceConnected: {\n                data.enforceInterface(DESCRIPTOR);\n                final ComponentName name;\n                if (0 != data.readInt()) {\n                    name = ComponentName.CREATOR.createFromParcel(data);\n                } else {\n                    name = null;\n                }\n                IBinder service;\n                service = data.readStrongBinder();\n                mPsc.onServiceConnected(name, service);\n\n                service.linkToDeath(new DeathRecipient() {\n                    @Override\n                    public void binderDied() {\n                        mPsc.onServiceDisconnected(name);\n                    }\n                }, 0);\n\n                reply.writeNoException();\n                return true;\n            }\n            case TRANSACTION_onServiceDisconnected: {\n                data.enforceInterface(DESCRIPTOR);\n                ComponentName _arg0;\n                if (0 != data.readInt()) {\n                    _arg0 = ComponentName.CREATOR.createFromParcel(data);\n                } else {\n                    _arg0 = null;\n                }\n                mPsc.onServiceDisconnected(_arg0);\n                reply.writeNoException();\n                return true;\n            }\n        }\n        return super.onTransact(code, data, reply, flags);\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/UuidManagerBinder.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.manager;\n\nimport android.os.Parcel;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.NotFoundException;\nimport com.tencent.shadow.dynamic.host.UuidManager;\n\nclass UuidManagerBinder extends android.os.Binder {\n\n    final private UuidManagerImpl mUuidManager;\n\n    UuidManagerBinder(UuidManagerImpl uuidManager) {\n        mUuidManager = uuidManager;\n    }\n\n    @Override\n    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {\n        switch (code) {\n            case INTERFACE_TRANSACTION: {\n                reply.writeString(UuidManager.DESCRIPTOR);\n                return true;\n            }\n            case UuidManager.TRANSACTION_getPlugin: {\n                data.enforceInterface(UuidManager.DESCRIPTOR);\n                String _arg0;\n                _arg0 = data.readString();\n                String _arg1;\n                _arg1 = data.readString();\n                try {\n                    InstalledApk _result = mUuidManager.getPlugin(_arg0, _arg1);\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NO_EXCEPTION);\n                    if ((_result != null)) {\n                        reply.writeInt(1);\n                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);\n                    } else {\n                        reply.writeInt(0);\n                    }\n                } catch (NotFoundException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NOT_FOUND_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                } catch (FailedException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case UuidManager.TRANSACTION_getPluginLoader: {\n                data.enforceInterface(UuidManager.DESCRIPTOR);\n                String _arg0;\n                _arg0 = data.readString();\n                try {\n                    InstalledApk _result = mUuidManager.getPluginLoader(_arg0);\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NO_EXCEPTION);\n                    if ((_result != null)) {\n                        reply.writeInt(1);\n                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);\n                    } else {\n                        reply.writeInt(0);\n                    }\n                } catch (NotFoundException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NOT_FOUND_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                } catch (FailedException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            case UuidManager.TRANSACTION_getRuntime: {\n                data.enforceInterface(UuidManager.DESCRIPTOR);\n                String _arg0;\n                _arg0 = data.readString();\n                try {\n                    InstalledApk _result = mUuidManager.getRuntime(_arg0);\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NO_EXCEPTION);\n                    if ((_result != null)) {\n                        reply.writeInt(1);\n                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);\n                    } else {\n                        reply.writeInt(0);\n                    }\n                } catch (NotFoundException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_NOT_FOUND_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                } catch (FailedException e) {\n                    reply.writeInt(UuidManager.TRANSACTION_CODE_FAILED_EXCEPTION);\n                    e.writeToParcel(reply, 0);\n                }\n                return true;\n            }\n            default:\n                return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager/src/main/java/com/tencent/shadow/dynamic/manager/UuidManagerImpl.java",
    "content": "package com.tencent.shadow.dynamic.manager;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.NotFoundException;\n\npublic interface UuidManagerImpl {\n    InstalledApk getPlugin(String uuid, String partKey) throws NotFoundException, FailedException;\n\n    InstalledApk getPluginLoader(String uuid) throws NotFoundException, FailedException;\n\n    InstalledApk getRuntime(String uuid) throws NotFoundException, FailedException;\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager-multi-loader-ext/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager-multi-loader-ext/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ndependencies {\n    implementation 'com.tencent.shadow.core:manager'\n    implementation project(':dynamic-manager')\n    implementation project(':dynamic-loader')\n    compileOnly 'com.tencent.shadow.core:common'\n    compileOnly project(':dynamic-host-multi-loader-ext')\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager-multi-loader-ext/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/sdk/dynamic/dynamic-manager-multi-loader-ext/src/main/java/com/tencent/shadow/dynamic/manager/PluginManagerThatSupportMultiLoader.java",
    "content": "package com.tencent.shadow.dynamic.manager;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.os.DeadObjectException;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.dynamic.host.PpsStatus;\nimport com.tencent.shadow.dynamic.host.MultiLoaderPluginProcessService;\nimport com.tencent.shadow.dynamic.host.MultiLoaderPpsController;\nimport com.tencent.shadow.dynamic.loader.PluginLoader;\n\n\nabstract public class PluginManagerThatSupportMultiLoader extends BaseDynamicPluginManager implements PluginManagerImpl {\n    private static final Logger mLogger = LoggerFactory.getLogger(PluginManagerThatUseDynamicLoader.class);\n\n    /**\n     * 插件进程MultiLoaderPluginProcessService的接口\n     */\n    protected MultiLoaderPpsController mPpsController;\n\n    /**\n     * 插件加载服务端接口\n     */\n    protected PluginLoader mPluginLoader;\n\n    public PluginManagerThatSupportMultiLoader(Context context) {\n        super(context);\n    }\n\n    /**\n     * 多Loader的PPS，需要hack多个RuntimeContainer，因此需要使用pluginKey来作为插件业务的身份标识\n     * Note：一个插件包有一份loader、一份runtime、多个pluginPart，该key与插件包一一对应\n     */\n    public abstract String getPluginKey();\n\n    @Override\n    protected void onPluginServiceConnected(ComponentName name, IBinder service) {\n        mPpsController = MultiLoaderPluginProcessService.wrapBinder(service);\n        try {\n            mPpsController.setUuidManagerForPlugin(getPluginKey(), new UuidManagerBinder(PluginManagerThatSupportMultiLoader.this));\n        } catch (DeadObjectException e) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"onServiceConnected RemoteException:\" + e);\n            }\n        } catch (RemoteException e) {\n            if (e.getClass().getSimpleName().equals(\"TransactionTooLargeException\")) {\n                if (mLogger.isErrorEnabled()) {\n                    mLogger.error(\"onServiceConnected TransactionTooLargeException:\" + e);\n                }\n            } else {\n                throw new RuntimeException(e);\n            }\n        }\n\n        try {\n            IBinder iBinder = mPpsController.getPluginLoaderForPlugin(getPluginKey());\n            if (iBinder != null) {\n                mPluginLoader = new BinderPluginLoader(iBinder);\n            }\n        } catch (RemoteException ignored) {\n            if (mLogger.isErrorEnabled()) {\n                mLogger.error(\"onServiceConnected mPpsController getPluginLoader:\", ignored);\n            }\n        }\n    }\n\n    @Override\n    protected void onPluginServiceDisconnected(ComponentName name) {\n        mPpsController = null;\n        mPluginLoader = null;\n    }\n\n    public final void loadRunTime(String uuid) throws RemoteException, FailedException {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadRunTime mPpsController:\" + mPpsController);\n        }\n        PpsStatus ppsStatus = mPpsController.getPpsStatusForPlugin(getPluginKey());\n        if (!ppsStatus.runtimeLoaded) {\n            mPpsController.loadRuntimeForPlugin(getPluginKey(), uuid);\n        }\n    }\n\n    public final void loadPluginLoader(String uuid) throws RemoteException, FailedException {\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"loadPluginLoader mPluginLoader:\" + mPluginLoader);\n        }\n        if (mPluginLoader == null) {\n            PpsStatus ppsStatus = mPpsController.getPpsStatusForPlugin(getPluginKey());\n            if (!ppsStatus.loaderLoaded) {\n                mPpsController.loadPluginLoaderForPlugin(getPluginKey(), uuid);\n            }\n            IBinder iBinder = mPpsController.getPluginLoaderForPlugin(getPluginKey());\n            mPluginLoader = new BinderPluginLoader(iBinder);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/sdk/dynamic/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "projects/sdk/dynamic/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\n"
  },
  {
    "path": "projects/sdk/dynamic/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/sdk/dynamic/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/sdk/dynamic/settings.gradle",
    "content": "includeBuild '../coding'\nincludeBuild '../core'\ninclude 'dynamic-manager',\n        'dynamic-apk',\n        'dynamic-host',\n        'dynamic-loader',\n        'dynamic-loader-impl',\n        'dynamic-host-multi-loader-ext',\n        'dynamic-manager-multi-loader-ext'"
  },
  {
    "path": "projects/test/common-jar-settings-test/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/common-jar-settings-test/build.gradle",
    "content": "apply plugin: 'com.tencent.shadow.internal.common-jar-settings'\n\ntask disassembleTestClass {\n    dependsOn(compileJava)\n    def classFile = file('build/classes/java/main/Test.class')\n    def outputFile = file('build/classes/java/main/Test.javap.txt')\n    inputs.file(classFile)\n    outputs.file(outputFile)\n    doLast {\n        def proc = \"javap -c ${classFile.path}\".split(' ').execute()\n        def outputStream = outputFile.newOutputStream()\n        proc.waitForProcessOutput(outputStream, System.err)\n        outputStream.close()\n        if (proc.exitValue() != 0) {\n            throw new GradleException(\"proc.exitValue() != 0\")\n        }\n    }\n}\n\n/**\n * 通过验证Test.java编译后的反编译结果Test.javap.txt，\n * 从中找出ByteBuffer.position的方法签名返回类型是ByteBuffer，还是Buffer，\n * 来确定common-jar-settings是否正确设置了android.jar作为Bootclasspath。\n *\n * 如果没有设置android.jar作为Bootclasspath，在用JDK11编译时，\n * 应用了common-jar-settings的模块可能会调用android.jar中不存在的方法，\n * 而这些模块应该是允许在Android设备上的。\n */\ntask testJavacBootclasspath {\n    dependsOn(disassembleTestClass)\n    def javapFile = file('build/classes/java/main/Test.javap.txt')\n    doLast {\n        def found = false\n        javapFile.eachLine {\n            if (it.contains(\"// Method java/nio/ByteBuffer.position:(I)Ljava/nio/Buffer;\")) {\n                found = true\n            }\n        }\n\n        if (!found) {\n            throw new GradleException(\"没有在javap结果中找到预期方法签名，详见测试方法注释\")\n        }\n    }\n}"
  },
  {
    "path": "projects/test/common-jar-settings-test/src/main/java/Test.java",
    "content": "import java.nio.ByteBuffer;\n\nclass Test {\n    public static void test(ByteBuffer byteBuffer) {\n        byteBuffer.position(0);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.CustomAndroidJUnitRunner\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n    sourceSets {\n        debug {\n            assets.srcDir('build/generated/assets/test-dynamic-manager/debug/')\n            assets.srcDir('build/generated/assets/plugin-zip/debug/')\n        }\n    }\n    lintOptions {\n        checkReleaseBuilds false\n        abortOnError false\n    }\n    testOptions {\n        animationsDisabled = true\n    }\n    flavorDimensions \"targetSdk\"\n    productFlavors {\n        target28 {\n            //target28就是defaultConfig\n        }\n        target34 {\n            minSdkVersion 19\n            targetSdkVersion 34\n        }\n    }\n}\n\ndependencies {\n    implementation \"commons-io:commons-io:$commons_io_android_version\"//example复制apk用的.\n    implementation \"org.slf4j:slf4j-api:$slf4j_version\"\n\n    implementation 'com.tencent.shadow.core:common'\n    implementation 'com.tencent.shadow.dynamic:dynamic-host'\n    implementation project(':constant')\n\n    testImplementation \"junit:junit:$junit_version\"\n    androidTestImplementation \"androidx.test.ext:junit:$androidx_test_junit_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-core:$espresso_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-remote:$espresso_version\"\n    implementation \"androidx.test.espresso:espresso-idling-resource:$espresso_version\"\n\n    implementation project(':plugin-use-host-code-lib')\n    implementation project(':test-manager')\n    implementation project(':custom-view')\n\n    androidTestTarget28Implementation \"androidx.test:core:$androidx_test_version\"\n    androidTestTarget34Implementation \"androidx.test:core:1.6.1\"\n\n    androidTestTarget28Implementation \"androidx.test:runner:$androidx_test_version\"\n    androidTestTarget34Implementation \"androidx.test:runner:1.6.1\"\n}\n\ndef createCopyTask(projectName, buildType, name, apkName, inputFile, taskName, forTask) {\n    def outputFile = file(\"${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}\")\n    outputFile.getParentFile().mkdirs()\n    return tasks.create(\"copy${buildType.capitalize()}${name.capitalize()}For${forTask.capitalize()}\", Copy) {\n        group = 'build'\n        description = \"复制${name}到assets中.\"\n        from(inputFile.getParent()) {\n            include(inputFile.name)\n            rename { outputFile.name }\n        }\n        into(outputFile.getParent())\n\n    }.dependsOn(\"${projectName}:${taskName}\")\n}\n\ndef generateAssets(generateAssetsTask, buildType) {\n\n    def moduleName = 'test-dynamic-manager'\n    def pluginManagerApkFile = file(\n            \"${project(\":test-dynamic-manager\").getBuildDir()}\" +\n                    \"/outputs/apk/${buildType}/\" +\n                    \"${moduleName}-${buildType}.apk\"\n    )\n    generateAssetsTask.dependsOn createCopyTask(\n            ':test-dynamic-manager',\n            buildType,\n            moduleName,\n            'pluginmanager.apk',\n            pluginManagerApkFile,\n            \"assemble${buildType.capitalize()}\",\n            generateAssetsTask.name\n    )\n\n    def pluginZip = file(\"${getRootProject().getBuildDir()}/plugin-${buildType}.zip\")\n    generateAssetsTask.dependsOn createCopyTask(\n            ':test-plugin-general-cases',\n            buildType,\n            'plugin-zip',\n            \"plugin-${buildType}.zip\",\n            pluginZip,\n            \"package${buildType.capitalize()}Plugin\",\n            generateAssetsTask.name\n    )\n\n\n}\n\ntasks.whenTaskAdded { task ->\n    if (task.name == \"generateTarget28DebugAssets\" || task.name == \"generateTarget34DebugAssets\") {\n        generateAssets(task, 'debug')\n    }\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.runtime.container.**{*;}\n\n#--start 下面是为了keep 插件访问宿主的白名单类--\n-keep class com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces.**{*;}\n-keep class com.tencent.shadow.test.lib.plugin_use_host_code_lib.other.**{*;}\n#--end--"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"comcom.tencent.shadow.test.hostapp.test\">\n\n    <!-- Multi-Process Espresso only works on API level 26 and above-->\n    <uses-sdk\n        android:minSdkVersion=\"26\"\n        android:targetSdkVersion=\"28\" />\n\n    <!--\n      This custom androidTest/AndroidManifest.xml is required because the current Gradle DSL\n      doesn't support the ability to define the new \"targetProcesses\" Instrumentation attribute and\n      to define custom \"meta-data\" tags that are required in this case.\n    -->\n\n    <instrumentation\n        android:name=\"com.tencent.shadow.test.CustomAndroidJUnitRunner\"\n        android:targetPackage=\"com.tencent.shadow.test.hostapp\">\n        <!-- 不设置targetProcesses为*是因为.plugin_service进程在PluginServiceConnectionTest中会被kill掉 -->\n\n        <!--\n          The following is used by AndroidJUnitRunner (AJUR) to init Espresso in the new PID. This\n          is mandatory and is required because AJUR is automatically instantiated by the Android\n          framework itself since it's simply an Instrumentation. However, Espresso is just a testing\n          framework that is used by AJUR so it's responsible to instantiate it on the remote process\n          in order for handle all the actions from the original process.\n        -->\n        <meta-data\n            android:name=\"remoteMethod\"\n            android:value=\"androidx.test.espresso.remote.EspressoRemote#remoteInit\" />\n\n    </instrumentation>\n</manifest>\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage com.google.devtools.build.android.desugar.runtime;\n\nimport java.io.Closeable;\nimport java.io.PrintStream;\nimport java.io.PrintWriter;\nimport java.lang.ref.Reference;\nimport java.lang.ref.ReferenceQueue;\nimport java.lang.ref.WeakReference;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.List;\nimport java.util.Vector;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * This is an extension class for java.lang.Throwable. It emulates the methods\n * addSuppressed(Throwable) and getSuppressed(), so the language feature try-with-resources can be\n * used on Android devices whose API level is below 19.\n *\n * <p>Note that the Desugar should avoid desugaring this class.\n */\npublic final class ThrowableExtension {\n\n    static final AbstractDesugaringStrategy STRATEGY;\n    /**\n     * This property allows users to change the desugared behavior of try-with-resources at runtime.\n     * If its value is {@code true}, then {@link MimicDesugaringStrategy} will NOT be used, and {@link\n     * NullDesugaringStrategy} is used instead.\n     *\n     * <p>Note: this property is ONLY used when the API level on the device is below 19.\n     */\n    public static final String SYSTEM_PROPERTY_TWR_DISABLE_MIMIC =\n            \"com.google.devtools.build.android.desugar.runtime.twr_disable_mimic\";\n\n    // Visible for testing.\n    static final int API_LEVEL;\n\n    static {\n        AbstractDesugaringStrategy strategy;\n        Integer apiLevel = null;\n        try {\n            apiLevel = readApiLevelFromBuildVersion();\n            if (apiLevel != null && apiLevel.intValue() >= 19) {\n                strategy = new ReuseDesugaringStrategy();\n            } else if (useMimicStrategy()) {\n                strategy = new MimicDesugaringStrategy();\n            } else {\n                strategy = new NullDesugaringStrategy();\n            }\n        } catch (Throwable e) {\n            // This catchall block is intentionally created to avoid anything unexpected, so that\n            // the desugared app will continue running in case of exceptions.\n            System.err.println(\n                    \"An error has occurred when initializing the try-with-resources desuguring strategy. \"\n                            + \"The default strategy \"\n                            + NullDesugaringStrategy.class.getName()\n                            + \"will be used. The error is: \");\n            e.printStackTrace(System.err);\n            strategy = new NullDesugaringStrategy();\n        }\n        STRATEGY = strategy;\n        API_LEVEL = apiLevel == null ? 1 : apiLevel.intValue();\n    }\n\n    public static AbstractDesugaringStrategy getStrategy() {\n        return STRATEGY;\n    }\n\n    public static void addSuppressed(Throwable receiver, Throwable suppressed) {\n        STRATEGY.addSuppressed(receiver, suppressed);\n    }\n\n    public static Throwable[] getSuppressed(Throwable receiver) {\n        return STRATEGY.getSuppressed(receiver);\n    }\n\n    public static void printStackTrace(Throwable receiver) {\n        STRATEGY.printStackTrace(receiver);\n    }\n\n    public static void printStackTrace(Throwable receiver, PrintWriter writer) {\n        STRATEGY.printStackTrace(receiver, writer);\n    }\n\n    public static void printStackTrace(Throwable receiver, PrintStream stream) {\n        STRATEGY.printStackTrace(receiver, stream);\n    }\n\n    public static void closeResource(Throwable throwable, Object resource) throws Throwable {\n        if (resource == null) {\n            return;\n        }\n        try {\n            if (API_LEVEL >= 19) {\n                ((AutoCloseable) resource).close();\n            } else {\n                if (resource instanceof Closeable) {\n                    ((Closeable) resource).close();\n                } else {\n                    try {\n                        Method method = resource.getClass().getMethod(\"close\");\n                        method.invoke(resource);\n                    } catch (NoSuchMethodException | SecurityException e) {\n                        throw new AssertionError(resource.getClass() + \" does not have a close() method.\", e);\n                    } catch (IllegalAccessException\n                            | IllegalArgumentException\n                            | ExceptionInInitializerError e) {\n                        throw new AssertionError(\"Fail to call close() on \" + resource.getClass(), e);\n                    } catch (InvocationTargetException e) {\n                        // Exception occurs during the invocation to the close method. The cause is the real\n                        // exception.\n                        Throwable cause = e.getCause();\n                        throw cause;\n                    }\n                }\n            }\n        } catch (Throwable e) {\n            if (throwable != null) {\n                addSuppressed(throwable, e);\n                throw throwable;\n            } else {\n                throw e;\n            }\n        }\n    }\n\n    private static boolean useMimicStrategy() {\n        return !Boolean.getBoolean(SYSTEM_PROPERTY_TWR_DISABLE_MIMIC);\n    }\n\n    private static final String ANDROID_OS_BUILD_VERSION = \"android.os.Build$VERSION\";\n\n    /**\n     * Get the API level from {@link android.os.Build.VERSION} via reflection. The reason to use\n     * relection is to avoid dependency on {@link android.os.Build.VERSION}. The advantage of doing\n     * this is that even when you desugar a jar twice, and Desugars sees this class, there is no need\n     * to put {@link android.os.Build.VERSION} on the classpath.\n     *\n     * <p>Another reason of doing this is that it does not introduce any additional dependency into\n     * the input jars.\n     *\n     * @return The API level of the current device. If it is {@code null}, then it means there was an\n     * exception.\n     */\n    private static Integer readApiLevelFromBuildVersion() {\n        try {\n            Class<?> buildVersionClass = Class.forName(ANDROID_OS_BUILD_VERSION);\n            Field field = buildVersionClass.getField(\"SDK_INT\");\n            return (Integer) field.get(null);\n        } catch (Exception e) {\n            System.err.println(\n                    \"Failed to retrieve value from \"\n                            + ANDROID_OS_BUILD_VERSION\n                            + \".SDK_INT due to the following exception.\");\n            e.printStackTrace(System.err);\n            return null;\n        }\n    }\n\n    /**\n     * The strategy to desugar try-with-resources statements. A strategy handles the behavior of an\n     * exception in terms of suppressed exceptions and stack trace printing.\n     */\n    abstract static class AbstractDesugaringStrategy {\n\n        protected static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];\n\n        public abstract void addSuppressed(Throwable receiver, Throwable suppressed);\n\n        public abstract Throwable[] getSuppressed(Throwable receiver);\n\n        public abstract void printStackTrace(Throwable receiver);\n\n        public abstract void printStackTrace(Throwable receiver, PrintStream stream);\n\n        public abstract void printStackTrace(Throwable receiver, PrintWriter writer);\n    }\n\n    /**\n     * This strategy just delegates all the method calls to java.lang.Throwable.\n     */\n    static final class ReuseDesugaringStrategy extends AbstractDesugaringStrategy {\n\n        @Override\n        public void addSuppressed(Throwable receiver, Throwable suppressed) {\n            receiver.addSuppressed(suppressed);\n        }\n\n        @Override\n        public Throwable[] getSuppressed(Throwable receiver) {\n            return receiver.getSuppressed();\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver) {\n            receiver.printStackTrace();\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintStream stream) {\n            receiver.printStackTrace(stream);\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintWriter writer) {\n            receiver.printStackTrace(writer);\n        }\n    }\n\n    /**\n     * This strategy mimics the behavior of suppressed exceptions with a map.\n     */\n    static final class MimicDesugaringStrategy extends AbstractDesugaringStrategy {\n\n        static final String SUPPRESSED_PREFIX = \"Suppressed: \";\n        private final ConcurrentWeakIdentityHashMap map = new ConcurrentWeakIdentityHashMap();\n\n        /**\n         * Suppress an exception. If the exception to be suppressed is {@receiver} or {@null}, an\n         * exception will be thrown.\n         */\n        @Override\n        public void addSuppressed(Throwable receiver, Throwable suppressed) {\n            if (suppressed == receiver) {\n                throw new IllegalArgumentException(\"Self suppression is not allowed.\", suppressed);\n            }\n            if (suppressed == null) {\n                throw new NullPointerException(\"The suppressed exception cannot be null.\");\n            }\n            // The returned list is a synchrnozed list.\n            map.get(receiver, /*createOnAbsence=*/true).add(suppressed);\n        }\n\n        @Override\n        public Throwable[] getSuppressed(Throwable receiver) {\n            List<Throwable> list = map.get(receiver, /*createOnAbsence=*/false);\n            if (list == null || list.isEmpty()) {\n                return EMPTY_THROWABLE_ARRAY;\n            }\n            return list.toArray(EMPTY_THROWABLE_ARRAY);\n        }\n\n        /**\n         * Print the stack trace for the parameter {@code receiver}. Note that it is deliberate to NOT\n         * reuse the implementation {@code MimicDesugaringStrategy.printStackTrace(Throwable,\n         * PrintStream)}, because we are not sure whether the developer prints the stack trace to a\n         * different stream other than System.err. Therefore, it is a caveat that the stack traces of\n         * {@code receiver} and its suppressed exceptions are printed in two different streams.\n         */\n        @Override\n        public void printStackTrace(Throwable receiver) {\n            receiver.printStackTrace();\n            List<Throwable> suppressedList = map.get(receiver, /*createOnAbsence=*/false);\n            if (suppressedList == null) {\n                return;\n            }\n            synchronized (suppressedList) {\n                for (Throwable suppressed : suppressedList) {\n                    System.err.print(SUPPRESSED_PREFIX);\n                    suppressed.printStackTrace();\n                }\n            }\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintStream stream) {\n            receiver.printStackTrace(stream);\n            List<Throwable> suppressedList = map.get(receiver, /*createOnAbsence=*/false);\n            if (suppressedList == null) {\n                return;\n            }\n            synchronized (suppressedList) {\n                for (Throwable suppressed : suppressedList) {\n                    stream.print(SUPPRESSED_PREFIX);\n                    suppressed.printStackTrace(stream);\n                }\n            }\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintWriter writer) {\n            receiver.printStackTrace(writer);\n            List<Throwable> suppressedList = map.get(receiver, /*createOnAbsence=*/false);\n            if (suppressedList == null) {\n                return;\n            }\n            synchronized (suppressedList) {\n                for (Throwable suppressed : suppressedList) {\n                    writer.print(SUPPRESSED_PREFIX);\n                    suppressed.printStackTrace(writer);\n                }\n            }\n        }\n    }\n\n    /**\n     * A hash map, that is concurrent, weak-key, and identity-hashing.\n     */\n    static final class ConcurrentWeakIdentityHashMap {\n\n        private final ConcurrentHashMap<WeakKey, List<Throwable>> map =\n                new ConcurrentHashMap<>(16, 0.75f, 10);\n        private final ReferenceQueue<Throwable> referenceQueue = new ReferenceQueue<>();\n\n        /**\n         * @param throwable,      the key to retrieve or create associated list.\n         * @param createOnAbsence {@code true} to create a new list if there is no value for the key.\n         * @return the associated value with the given {@code throwable}. If {@code createOnAbsence} is\n         * {@code true}, the returned value will be non-null. Otherwise, it can be {@code null}\n         */\n        public List<Throwable> get(Throwable throwable, boolean createOnAbsence) {\n            deleteEmptyKeys();\n            WeakKey keyForQuery = new WeakKey(throwable, null);\n            List<Throwable> list = map.get(keyForQuery);\n            if (!createOnAbsence) {\n                return list;\n            }\n            if (list != null) {\n                return list;\n            }\n            List<Throwable> newValue = new Vector<>(2);\n            list = map.putIfAbsent(new WeakKey(throwable, referenceQueue), newValue);\n            return list == null ? newValue : list;\n        }\n\n        /**\n         * For testing-purpose\n         */\n        int size() {\n            return map.size();\n        }\n\n        void deleteEmptyKeys() {\n            // The ReferenceQueue.poll() is thread-safe.\n            for (Reference<?> key = referenceQueue.poll(); key != null; key = referenceQueue.poll()) {\n                map.remove(key);\n            }\n        }\n\n        private static final class WeakKey extends WeakReference<Throwable> {\n\n            /**\n             * The hash code is used later to retrieve the entry, of which the key is the current weak\n             * key. If the referent is marked for garbage collection and is set to null, we are still able\n             * to locate the entry.\n             */\n            private final int hash;\n\n            public WeakKey(Throwable referent, ReferenceQueue<Throwable> q) {\n                super(referent, q);\n                if (referent == null) {\n                    throw new NullPointerException(\"The referent cannot be null\");\n                }\n                hash = System.identityHashCode(referent);\n            }\n\n            @Override\n            public int hashCode() {\n                return hash;\n            }\n\n            @Override\n            public boolean equals(Object obj) {\n                if (obj == null || obj.getClass() != getClass()) {\n                    return false;\n                }\n                if (this == obj) {\n                    return true;\n                }\n                WeakKey other = (WeakKey) obj;\n                // Note that, after the referent is garbage collected, then the referent will be null.\n                // And the equality test still holds.\n                return this.hash == other.hash && this.get() == other.get();\n            }\n        }\n    }\n\n    /**\n     * This strategy ignores all suppressed exceptions, which is how retrolambda does.\n     */\n    static final class NullDesugaringStrategy extends AbstractDesugaringStrategy {\n\n        @Override\n        public void addSuppressed(Throwable receiver, Throwable suppressed) {\n            // Do nothing. The suppressed exception is discarded.\n        }\n\n        @Override\n        public Throwable[] getSuppressed(Throwable receiver) {\n            return EMPTY_THROWABLE_ARRAY;\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver) {\n            receiver.printStackTrace();\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintStream stream) {\n            receiver.printStackTrace(stream);\n        }\n\n        @Override\n        public void printStackTrace(Throwable receiver, PrintWriter writer) {\n            receiver.printStackTrace(writer);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/CreateResourceTest.java",
    "content": "package com.tencent.shadow.test;\n\nimport com.tencent.shadow.test.cases.plugin_androidx.AppCompatActivityTest;\nimport com.tencent.shadow.test.cases.plugin_main.ThemeTest;\nimport com.tencent.shadow.test.cases.plugin_main.ViewIdTest;\n\nimport org.junit.Ignore;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Suite;\n\n/**\n * com.tencent.shadow.core.loader.blocs.CreateResourceBloc\n * 改动相关测试\n */\n@RunWith(Suite.class)\n@Ignore(\"避免自动化全量测试时重复这些测试\")\n@Suite.SuiteClasses({\n        ViewIdTest.class,\n        ThemeTest.class,\n        AppCompatActivityTest.class,\n})\npublic class CreateResourceTest {\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/CustomAndroidJUnitRunner.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test;\n\nimport android.os.Bundle;\n\nimport androidx.test.runner.AndroidJUnitRunner;\n\npublic class CustomAndroidJUnitRunner extends AndroidJUnitRunner {\n    @Override\n    public void onCreate(Bundle arguments) {\n        //禁止Google收集数据，避免因访问不到在测试结束后等待40秒超时\n        arguments.putString(\"disableAnalytics\", Boolean.toString(true));\n        super.onCreate(arguments);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/PluginTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test;\n\nimport android.app.Activity;\nimport android.content.Intent;\n\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.IdlingRegistry;\nimport androidx.test.espresso.IdlingResource;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.assertion.ViewAssertions;\nimport androidx.test.espresso.matcher.ViewMatchers;\n\nimport com.tencent.shadow.test.dynamic.host.HostApplication;\nimport com.tencent.shadow.test.dynamic.host.JumpToPluginActivity;\nimport com.tencent.shadow.test.dynamic.host.R;\nimport com.tencent.shadow.test.dynamic.host.SimpleIdlingResourceImpl;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport org.hamcrest.Matchers;\nimport org.junit.After;\nimport org.junit.Before;\n\npublic abstract class PluginTest {\n\n    /**\n     * 要启动的插件intent\n     *\n     * @return 插件Activity intent\n     */\n    abstract protected Intent getLaunchIntent();\n\n    /**\n     * 要启动的插件的PartKey\n     */\n    abstract protected String getPartKey();\n\n    /**\n     * 检测view\n     *\n     * @param tag  view的tag\n     * @param text view上的文字\n     */\n    public void matchTextWithViewTag(String tag, String text) {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(tag)))\n                .check(ViewAssertions.matches(ViewMatchers.withText(text)));\n    }\n\n    public void matchSubstringWithViewTag(String tag, String text) {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(tag)))\n                .check(ViewAssertions.matches(ViewMatchers.withSubstring(text)));\n    }\n\n    @Before\n    public void launchActivity() {\n        IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();\n\n        // 清理上一个测试失败时可能遗留的锁\n        for (IdlingResource resource : idlingRegistry.getResources()) {\n            idlingRegistry.unregister(resource);\n        }\n\n        SimpleIdlingResourceImpl idlingResource = HostApplication.getApp().mIdlingResource;\n        idlingResource.setIdleState(true);\n        idlingRegistry.register(idlingResource);\n        TestManager.TheSimpleIdlingResource = idlingResource;\n        launchJumpActivity(getPartKey(), getLaunchIntent());\n\n        Espresso.onView(ViewMatchers.withId(R.id.jump)).perform(ViewActions.click());\n    }\n\n\n    @After\n    public void unregisterIdlingResource() {\n        TestManager.TheSimpleIdlingResource = null;\n        SimpleIdlingResourceImpl idlingResource = HostApplication.getApp().mIdlingResource;\n        idlingResource.setIdleState(true);\n        IdlingRegistry.getInstance().unregister(idlingResource);\n    }\n\n    private void launchJumpActivity(String partKey, Intent pluginIntent) {\n        Intent intent = new Intent(ApplicationProvider.getApplicationContext(), getJumpActivityClass());\n        intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);\n        intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, pluginIntent.getComponent().getClassName());\n        intent.putExtra(Constant.KEY_EXTRAS, pluginIntent.getExtras());\n        intent.putExtra(Constant.KEY_FROM_ID, getFromId());\n        ActivityScenario.launch(intent);\n    }\n\n    protected Class<? extends Activity> getJumpActivityClass() {\n        return JumpToPluginActivity.class;\n    }\n\n    protected int getFromId() {\n        return Constant.FROM_ID_START_ACTIVITY;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_androidx/AppCompatActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_androidx;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class AppCompatActivityTest extends PluginAndroidxAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.androidx_cases.lib.AppCompatTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testFactory2Class() {\n        matchTextWithViewTag(\"factory2Class\", \"androidx.appcompat.app.AppCompatDelegateImpl\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_androidx/ComponentFactoryTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_androidx;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ComponentFactoryTest extends PluginAndroidxAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.androidx_cases.lib.AppComponentFactoryTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testActivityCreateByComponentFactory() {\n        matchTextWithViewTag(\"flag\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_androidx/LiveDataWithActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_androidx;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class LiveDataWithActivityTest extends PluginAndroidxAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.androidx_cases.lib.LiveDataWithActivityTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testLiveDataWithActivity() {\n        matchTextWithViewTag(\"data\", \"\");\n\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button\"))).perform(ViewActions.click());\n\n        matchTextWithViewTag(\"data\", \"onClick\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_androidx/PluginAndroidxAppTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_androidx;\n\nimport com.tencent.shadow.test.PluginTest;\nimport com.tencent.shadow.test.lib.constant.Constant;\n\npublic abstract class PluginAndroidxAppTest extends PluginTest {\n\n    @Override\n    protected String getPartKey() {\n        return Constant.PART_KEY_PLUGIN_ANDROIDX;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ActivityContextSubDirTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\npublic class ActivityContextSubDirTest extends PluginMainAppTest {\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.ActivityContextSubDirTestActivity\"\n        );\n        return pluginIntent;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ActivityLifecycleCallbacksTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\nimport android.os.Build;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class ActivityLifecycleCallbacksTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.ActivityLifecycleCallbacksTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n\n    @Test\n    public void testRecreateRecord() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"recreate\")))\n                .perform(ViewActions.click());\n\n        String api29 = \"[onActivityPreCreated, \" +\n                \"onActivityCreated, \" +\n                \"onActivityPostCreated, \" +\n                \"onActivityPreStarted, \" +\n                \"onActivityStarted, \" +\n                \"onActivityPostStarted, \" +\n                \"onActivityPreResumed, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPostResumed, \" +\n                \"onActivityPrePaused, \" +\n                \"onActivityPaused, \" +\n                \"onActivityPostPaused, \" +\n                \"onActivityPreStopped, \" +\n                \"onActivityStopped, \" +\n                \"onActivityPostStopped, \" +\n                \"onActivityPreSaveInstanceState, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityPostSaveInstanceState, \" +\n                \"onActivityPreDestroyed, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityPostDestroyed, \" +\n                \"onActivityPreCreated, \" +\n                \"onActivityCreated]\";\n\n        String api28 = \"[onActivityCreated, \" +\n                \"onActivityStarted, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPaused, \" +\n                \"onActivityStopped, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityCreated]\";\n\n        String api27 = \"[onActivityCreated, \" +\n                \"onActivityStarted, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPaused, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityStopped, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityCreated]\";\n\n        String expect;\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {\n            expect = api27;\n        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {\n            expect = api28;\n        } else {\n            expect = api29;\n        }\n\n        matchTextWithViewTag(\"ActivityCreateTest\", expect);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ActivityReCreateTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ActivityReCreateTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestActivityReCreate\"\n        );\n        pluginIntent.putExtra(\"reCreateMsg\", \"beforeReCreate\");\n        return pluginIntent;\n    }\n\n    @Test\n    public void testOnCreate() {\n        matchTextWithViewTag(\"tv_msg\", \"reCreateMsg:beforeReCreate\");\n    }\n\n    @Test\n    public void testReCreate() {\n        matchTextWithViewTag(\"tv_msg\", \"reCreateMsg:beforeReCreate\");\n\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button\"))).perform(ViewActions.click());\n\n        matchTextWithViewTag(\"tv_msg\", \"reCreateMsg:afterReCreate\");\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ActivityWindowSoftModeTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ActivityWindowSoftModeTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestActivityWindowSoftMode\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testBasicUsage() {\n\n        matchTextWithViewTag(\"TAG_SOFT_MODE\", \"SOFT_INPUT_STATE_VISIBLE:true\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ApplicationContextInActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ApplicationContextInActivityTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.ApplicationContextActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testUseApplicationContextInAttachBaseContext() {\n        matchTextWithViewTag(\"noError\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ApplicationContextSubDirTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\npublic class ApplicationContextSubDirTest extends SubDirContextThemeWrapperTest {\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.ApplicationContextSubDirTestActivity\"\n        );\n        return pluginIntent;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ApplicationTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\nimport android.os.Build;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Assume;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ApplicationTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.TestApplicationActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testApplication() {\n        matchTextWithViewTag(\"TAG_IS_ON_CREATE\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testApplicationGetProcessName() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);\n        matchTextWithViewTag(\"Application.getProcessName()\",\n                ApplicationProvider.getApplicationContext().getPackageName());\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/BasicTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class BasicTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestActivityOnCreate\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testBasicUsage() {\n\n        matchTextWithViewTag(\"tv_msg\", \"Activity生命周期测试\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/BootClassloaderTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class BootClassloaderTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.classloader.TestBootClassloaderActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testBasicUsage() {\n\n        matchTextWithViewTag(\"xmlPullParserFrom\", \"java.lang.BootClassLoader\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ContextGetPackageCodePathTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ContextGetPackageCodePathTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.ContextGetPackageCodePathTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testGetPackageCodePath() {\n        matchSubstringWithViewTag(\"PackageCodePath\", \"/plugin-debug.zip/test-plugin-general-cases-plugin-debug.apk\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/GetCallingActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\n\npublic class GetCallingActivityTest extends PluginMainAppTest {\n\n    public static final String PrintActivityResultActivity = \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.PrintActivityResultActivity\";\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                PrintActivityResultActivity\n        );\n        pluginIntent.putExtra(\"targetClassName\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestCallingActivity\");\n        pluginIntent.putExtra(\"waitForResult\", false);\n        return pluginIntent;\n    }\n\n    @Test\n    public void testGetCallingActivity() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button\"))).perform(ViewActions.click());\n\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        ComponentName componentName = new ComponentName(packageName, PrintActivityResultActivity);\n        matchTextWithViewTag(\"TAG_GET_CALLING_ACTIVITY\", componentName.toShortString());\n    }\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/HostInterfaceTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\nimport org.junit.Test;\n\n/**\n * 插件访问宿主类的白名单机制测试用例\n */\npublic class HostInterfaceTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.interfaces.TestHostInterfaceActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testInWhitelist() {\n        matchTextWithViewTag(\"TAG_loadClass_in_whitelist\",\n                Boolean.toString(true));\n    }\n\n    @Test\n    public void testNotInWhitelistOtherPackage() {\n        matchTextWithViewTag(\"TAG_loadClass_not_in_whitelist_other_package\",\n                Boolean.toString(false));\n    }\n\n    @Test\n    public void testNotInWhitelistSubPackage() {\n        matchTextWithViewTag(\"TAG_loadClass_not_in_whitelist_sub_package\",\n                Boolean.toString(false));\n    }\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/InstrumentationTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\nimport org.junit.Test;\n\npublic class InstrumentationTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.instrumentation.TestInstrumentationActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testStaticMethod() {\n        matchTextWithViewTag(\"newApplicationSuccess\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testSubClassMemberMethod() {\n        matchTextWithViewTag(\"callActivityOnDestroySuccess\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testNewApplicationSuccess1() {\n        matchTextWithViewTag(\"newApplicationSuccess1\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testNewShadowActivitySuccess() {\n        matchTextWithViewTag(\"newShadowActivitySuccess\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testCallApplicationOnCreateSuccess() {\n        matchTextWithViewTag(\"callApplicationOnCreateSuccess\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testCallActivityOnCreateSuccess() {\n        matchTextWithViewTag(\"callActivityOnCreateSuccess\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testCallActivityOnCreateSuccess1() {\n        matchTextWithViewTag(\"callActivityOnCreateSuccess1\", Boolean.toString(true));\n    }\n\n    @Test\n    public void testExecStartActivitySuccess() {\n        matchTextWithViewTag(\"execStartActivitySuccess\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/LayoutInflaterTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class LayoutInflaterTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.TestLayoutInflaterActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testFactoryClassNameBeforeSet() {\n        matchTextWithViewTag(\"FactoryClassNameBeforeSet\", \"null\");\n    }\n\n    @Test\n    public void testFactoryClassNameAfterSet() {\n        matchTextWithViewTag(\"FactoryClassNameAfterSet\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.TestFactory2\");\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/NullRefInXmlTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class NullRefInXmlTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.view.TestNullRefInXmlActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testColorOfNull() {\n        matchTextWithViewTag(\"cacheColorHint\", \"0\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/PackageManagerTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\n\nimport androidx.test.core.app.ApplicationProvider;\n\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport org.junit.Test;\n\npublic class PackageManagerTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager.TestPackageManagerActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testGetApplicationInfoClassName() {\n        matchTextWithViewTag(\"getApplicationInfo/className\", \"com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication\");\n    }\n\n    @Test\n    public void testGetApplicationInfoNativeLibraryDir() {\n        matchSubstringWithViewTag(\"getApplicationInfo/nativeLibraryDir\", TestManager.uuid + \"_lib\");\n    }\n\n    @Test\n    public void testGetApplicationInfoMetaData() {\n        matchTextWithViewTag(\"getApplicationInfo/metaData\", \"test_value\");\n    }\n\n    @Test\n    public void testQueryContentProvidersName() {\n        matchTextWithViewTag(\"queryContentProviders/size\", \">0\");\n    }\n\n    @Test\n    public void testResolveActivityByExplicitIntent() {\n        matchTextWithViewTag(\"resolveActivity/explicit\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager.TestPackageManagerActivity\");\n    }\n\n    @Test\n    public void testGetServiceInfoName() {\n        matchTextWithViewTag(\"getServiceInfo/name\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.service.TestService\");\n    }\n\n    @Test\n    public void testGetServiceInfoPackageName() {\n        matchTextWithViewTag(\"getServiceInfo/packageName\", \"com.tencent.shadow.test.hostapp\");\n    }\n\n    @Test\n    public void testGetPackageInfoVersionName() throws PackageManager.NameNotFoundException {\n        Context applicationContext = ApplicationProvider.getApplicationContext();\n        String packageName = applicationContext.getPackageName();\n        PackageInfo packageInfo = applicationContext.getPackageManager().getPackageInfo(packageName, 0);\n        matchTextWithViewTag(\"getPackageInfo/versionName\", packageInfo.versionName);\n    }\n\n    @Test\n    public void testGetPackageInfoVersionCode() throws PackageManager.NameNotFoundException {\n        Context applicationContext = ApplicationProvider.getApplicationContext();\n        String packageName = applicationContext.getPackageName();\n        PackageInfo packageInfo = applicationContext.getPackageManager().getPackageInfo(packageName, 0);\n        matchTextWithViewTag(\"getPackageInfo/versionCode\", Integer.toString(packageInfo.versionCode));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/PluginBroadcastReceiverTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\n\n/**\n * 广播测试\n */\npublic class PluginBroadcastReceiverTest extends PluginMainAppTest {\n\n    private final static String INTENT_NORMAL_ACTION = \"com.tencent.test.normal.action\";\n    private final static String INTENT_DYNAMIC_ACTION = \"com.tencent.test.action.DYNAMIC\";\n\n    private final static String MSG_NORMAL = \"收到测试静态广播发送\";\n    private final static String MSG_DYNAMIC = \"收到动态动态广播发送\";\n\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.receiver.TestReceiverActivity\"\n        );\n        return pluginIntent;\n    }\n\n    /**\n     * 动态广播测试\n     */\n    @Test\n    public void testDynamicBroadcastReceiver() {\n        //测试动态广播可以正常收到，并且action，extra，以及context都是正确的\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button_dynamic\"))).\n                perform(ViewActions.click());\n        matchTextWithViewTag(\"text\",\n                String.format(\"action:%s msg:%s isShadowContext:%s\", INTENT_DYNAMIC_ACTION,\n                        MSG_DYNAMIC, \"true\"));\n\n        //测试动态广播反注册后就收不到广播了\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button_unRegister_dynamic\"))).\n                perform(ViewActions.click());\n        matchTextWithViewTag(\"text\", \"\");\n\n    }\n\n    /**\n     * 静态广播测试\n     */\n    @Test\n    public void testStaticBroadcastReceiver() {\n        //测试静态广播可以正常收到，并且action，extra，以及context都是正确的\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"button_static\"))).\n                perform(ViewActions.click());\n\n        matchTextWithViewTag(\"text\",\n                String.format(\"action:%s msg:%s isShadowContext:%s\", INTENT_NORMAL_ACTION,\n                        MSG_NORMAL, \"true\"));\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/PluginMainAppTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport com.tencent.shadow.test.PluginTest;\nimport com.tencent.shadow.test.lib.constant.Constant;\n\npublic abstract class PluginMainAppTest extends PluginTest {\n\n    @Override\n    protected String getPartKey() {\n        return Constant.PART_KEY_PLUGIN_MAIN_APP;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/PluginServiceTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class PluginServiceTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.service.TestStartServiceActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testService() {\n        // test startService\n        click(\"start\");\n        String text = \"onCreate-onStartCommand\";\n        check(text);\n\n        // test stopService\n        click(\"stop\");\n        text += \"-onDestroy\";\n        check(text);\n\n        // test bindService\n        click(\"bind\");\n        text += \"-onCreate-onBind\";\n        check(text);\n\n        // test callBinder\n        click(\"testBinder\");\n        text += \"-callTest\";\n        check(text);\n\n        // test unbindService\n        click(\"unbind\");\n        text += \"-onUnbind-onDestroy\";\n        check(text);\n\n        // test startService + bindService + stopService + unBind\n        click(\"start\");\n        text += \"-onCreate-onStartCommand\";\n        check(text);\n        click(\"bind\");\n        text += \"-onBind\";\n        check(text);\n        click(\"stop\");\n        check(text);\n        click(\"unbind\");\n        text += \"-onUnbind-onDestroy\";\n        check(text);\n    }\n\n    @Test\n    public void startInNewThread() {\n        click(\"startInNewThread\");\n        String text = \"onCreate-onStartCommand\";\n        check(text);\n\n        // test stopService\n        click(\"stop\");\n        text += \"-onDestroy\";\n        check(text);\n    }\n\n    private void click(String tag) {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(tag))).perform(ViewActions.click());\n    }\n\n    private void check(String text) {\n        matchTextWithViewTag(\"text\", text);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/RegisterNullReceiverTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Parcel;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport java.util.Arrays;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class RegisterNullReceiverTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.RegisterNullReceiverActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testRegisterNullReceiver() {\n        // 抄一段和RegisterNullReceiverActivity中构造数据一样的代码在Host中直接运行，\n        // 得到string跟插件环境下运行的相同代码对比\n        String string;\n        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);\n        Intent intent = ApplicationProvider.getApplicationContext().registerReceiver(null, intentFilter);\n\n        if (intent != null) {\n            Parcel parcel = Parcel.obtain();\n            intent.writeToParcel(parcel, 0);\n            byte[] byteArray = parcel.marshall();\n            Assert.assertTrue(byteArray.length > 0);\n            string = Arrays.toString(byteArray);\n            parcel.recycle();\n        } else {\n            string = \"intent == null\";\n        }\n\n        matchTextWithViewTag(\"byteArray\", string);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ReinstallPluginTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport com.tencent.shadow.test.lib.constant.Constant;\n\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ReinstallPluginTest extends BasicTest {\n\n    @Override\n    protected int getFromId() {\n        return Constant.FROM_ID_REINSTALL_PLUGIN;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ServiceContextSubDirTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\npublic class ServiceContextSubDirTest extends SubDirContextThemeWrapperTest {\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.ServiceContextSubDirTestActivity\"\n        );\n        return pluginIntent;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/SubDirContextThemeWrapperTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport static android.content.Context.MODE_PRIVATE;\nimport static android.os.Environment.DIRECTORY_MUSIC;\nimport static android.os.Environment.DIRECTORY_PODCASTS;\n\nimport android.content.SharedPreferences;\nimport android.os.Build;\n\nimport androidx.test.core.app.ApplicationProvider;\n\nimport org.junit.Assert;\nimport org.junit.Assume;\nimport org.junit.Test;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.Arrays;\n\nabstract class SubDirContextThemeWrapperTest extends PluginMainAppTest {\n\n    private static final String PREFIX = \"ShadowPlugin\";\n    private static final String BUSINESS_NAME = \"general-cases\";\n    private static final String EXPECT_NAME = PREFIX + \"_\" + BUSINESS_NAME;\n\n    @Test\n    public void testGetDataDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);\n        String hostDataDir = ApplicationProvider.getApplicationContext().getDataDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_DATA_DIR\", hostDataDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetFilesDir() {\n        String hostFilesDir = ApplicationProvider.getApplicationContext().getFilesDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_FILES_DIR\", hostFilesDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testOpenFileInput() {\n        String hostFilesDir = ApplicationProvider.getApplicationContext().getFilesDir().getAbsolutePath();\n        matchTextWithViewTag(\n                \"TAG_OPEN_FILE_INPUT_FOO\",\n                hostFilesDir + \"/\" + EXPECT_NAME + \"/\" + \"foo\"\n        );\n        matchTextWithViewTag(\n                \"TAG_OPEN_FILE_INPUT_BAR\",\n                hostFilesDir + \"/\" + EXPECT_NAME + \"/\" + \"bar\"\n        );\n    }\n\n    @Test\n    public void testOpenFileOutput() {\n        String hostFilesDir = ApplicationProvider.getApplicationContext().getFilesDir().getAbsolutePath();\n        matchTextWithViewTag(\n                \"TAG_OPEN_FILE_OUTPUT_FOO\",\n                hostFilesDir + \"/\" + EXPECT_NAME + \"/\" + \"foo\"\n        );\n        matchTextWithViewTag(\n                \"TAG_OPEN_FILE_OUTPUT_BAR\",\n                hostFilesDir + \"/\" + EXPECT_NAME + \"/\" + \"bar\"\n        );\n    }\n\n    @Test\n    public void testDeleteFile() {\n        matchTextWithViewTag(\n                \"TAG_DELETE_FILE_FOO\",\n                \"success\"\n        );\n        matchTextWithViewTag(\n                \"TAG_DELETE_FILE_BAR\",\n                \"success\"\n        );\n    }\n\n    @Test\n    public void testGetNoBackupFilesDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);\n        String hostDir = ApplicationProvider.getApplicationContext().getNoBackupFilesDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_NBF_DIR\", hostDir + \"/\" + EXPECT_NAME);\n\n    }\n\n    @Test\n    public void testGetExternalFilesDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n        String hostDir = ApplicationProvider.getApplicationContext().getExternalFilesDir(DIRECTORY_MUSIC).getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_EFD_MUSIC\", hostDir + \"/\" + EXPECT_NAME);\n        hostDir = ApplicationProvider.getApplicationContext().getExternalFilesDir(DIRECTORY_PODCASTS).getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_EFD_PODCASTS\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetExternalFilesDirs() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n\n        File[] hostDirs = ApplicationProvider.getApplicationContext().getExternalFilesDirs(DIRECTORY_MUSIC);\n        File[] expectsDirs = new File[hostDirs.length];\n        for (int i = 0; i < hostDirs.length; i++) {\n            File hostDir = hostDirs[i];\n            File expectDir = new File(hostDir, EXPECT_NAME);\n            expectsDirs[i] = expectDir;\n        }\n        matchTextWithViewTag(\n                \"TAG_GET_EFDS_MUSIC\",\n                Arrays.toString(expectsDirs)\n        );\n\n        hostDirs = ApplicationProvider.getApplicationContext().getExternalFilesDirs(DIRECTORY_PODCASTS);\n        expectsDirs = new File[hostDirs.length];\n        for (int i = 0; i < hostDirs.length; i++) {\n            File hostDir = hostDirs[i];\n            File expectDir = new File(hostDir, EXPECT_NAME);\n            expectsDirs[i] = expectDir;\n        }\n        matchTextWithViewTag(\n                \"TAG_GET_EFDS_PODCASTS\",\n                Arrays.toString(expectsDirs)\n        );\n    }\n\n    @Test\n    public void testGetObbDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n        String hostDir = ApplicationProvider.getApplicationContext().getObbDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_OBB_DIR\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetObbDirs() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n        File[] hostDirs = ApplicationProvider.getApplicationContext().getObbDirs();\n        File[] expectsDirs = new File[hostDirs.length];\n        for (int i = 0; i < hostDirs.length; i++) {\n            File hostDir = hostDirs[i];\n            File expectDir = new File(hostDir, EXPECT_NAME);\n            expectsDirs[i] = expectDir;\n        }\n        matchTextWithViewTag(\n                \"TAG_GET_OBB_DIRS\",\n                Arrays.toString(expectsDirs)\n        );\n    }\n\n    @Test\n    public void testGetCacheDir() {\n        String hostDir = ApplicationProvider.getApplicationContext().getCacheDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_CACHE_DIR\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetCodeCacheDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);\n        String hostDir = ApplicationProvider.getApplicationContext().getCodeCacheDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_CODE_CACHE_DIR\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetExternalCacheDir() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n        String hostDir = ApplicationProvider.getApplicationContext().getExternalCacheDir().getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_EXT_CACHE_DIR\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetExternalCacheDirs() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);\n        File[] hostDirs = ApplicationProvider.getApplicationContext().getExternalCacheDirs();\n        File[] expectsDirs = new File[hostDirs.length];\n        for (int i = 0; i < hostDirs.length; i++) {\n            File hostDir = hostDirs[i];\n            File expectDir = new File(hostDir, EXPECT_NAME);\n            expectsDirs[i] = expectDir;\n        }\n        matchTextWithViewTag(\n                \"TAG_GET_EXT_CACHE_DIRS\",\n                Arrays.toString(expectsDirs)\n        );\n\n    }\n\n    @Test\n    public void testGetExternalMediaDirs() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);\n        File[] hostDirs = ApplicationProvider.getApplicationContext().getExternalMediaDirs();\n        File[] expectsDirs = new File[hostDirs.length];\n        for (int i = 0; i < hostDirs.length; i++) {\n            File hostDir = hostDirs[i];\n            File expectDir = new File(hostDir, EXPECT_NAME);\n            expectsDirs[i] = expectDir;\n        }\n        matchTextWithViewTag(\n                \"TAG_GET_EXT_MEDIA_DIRS\",\n                Arrays.toString(expectsDirs)\n        );\n    }\n\n    @Test\n    public void testGetDir() {\n        String hostDir = ApplicationProvider.getApplicationContext().getDir(\"foo\", MODE_PRIVATE).getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_DIR_FOO\", hostDir + \"/\" + EXPECT_NAME);\n\n        hostDir = ApplicationProvider.getApplicationContext().getDir(\"bar\", MODE_PRIVATE).getAbsolutePath();\n        assertTextAndFileExist(\"TAG_GET_DIR_BAR\", hostDir + \"/\" + EXPECT_NAME);\n    }\n\n    @Test\n    public void testGetSharedPreferences() {\n        final String testSharedPreferences = \"testGetSharedPreferences\";\n        SharedPreferences sharedPreferences\n                = ApplicationProvider.getApplicationContext().getSharedPreferences(testSharedPreferences, MODE_PRIVATE);\n        boolean commit = sharedPreferences.edit().putString(\"test\", \"test\").commit();\n        if (!commit) {\n            throw new RuntimeException(\"commit failed\");\n        }\n\n        File sharedPrefs = new File(ApplicationProvider.getApplicationContext().getFilesDir().getParent(), \"shared_prefs\");\n        File[] files = sharedPrefs.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File dir, String name) {\n                if (name.contains(testSharedPreferences)) {\n                    return true;\n                } else {\n                    return false;\n                }\n            }\n        });\n        if (files.length != 1) {\n            throw new RuntimeException(\"匹配文件数量不对。\");\n        }\n        String hostSPFilePath = files[0].getParentFile().getAbsolutePath();\n\n        matchTextWithViewTag(\"TAG_GET_SP_FOO\", hostSPFilePath + \"/\" + EXPECT_NAME + \"_foo.xml\");\n        matchTextWithViewTag(\"TAG_GET_SP_BAR\", hostSPFilePath + \"/\" + EXPECT_NAME + \"_bar.xml\");\n    }\n\n    @Test\n    public void testDeleteSharedPreferences() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);\n        matchTextWithViewTag(\n                \"TAG_DEL_SP_FOO\",\n                \"success\"\n        );\n        matchTextWithViewTag(\n                \"TAG_DEL_SP_BAR\",\n                \"success\"\n        );\n    }\n\n    @Test\n    public void testOpenOrCreateDatabase() {\n        String hostDatabasePath = ApplicationProvider.getApplicationContext().getDatabasePath(\"test\").getParentFile().getAbsolutePath();\n        matchTextWithViewTag(\n                \"TAG_OOCD3_FOO\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_foo\"\n        );\n        matchTextWithViewTag(\n                \"TAG_OOCD3_BAR\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_bar\"\n        );\n        matchTextWithViewTag(\n                \"TAG_OOCD4_FOO\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_foo\"\n        );\n        matchTextWithViewTag(\n                \"TAG_OOCD4_BAR\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_bar\"\n        );\n    }\n\n    @Test\n    public void testMoveDatabaseFrom() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);\n        matchTextWithViewTag(\"TAG_MOVE_DB_FROM_FOO\", \"暂不支持\");\n        matchTextWithViewTag(\"TAG_MOVE_DB_FROM_BAR\", \"暂不支持\");\n    }\n\n    @Test\n    public void testDeleteDatabase() {\n        matchTextWithViewTag(\n                \"TAG_DELETE_DB_FOO\",\n                \"success\"\n        );\n        matchTextWithViewTag(\n                \"TAG_DELETE_DB_BAR\",\n                \"success\"\n        );\n    }\n\n    @Test\n    public void testGetDatabasePath() {\n        String hostDatabasePath = ApplicationProvider.getApplicationContext().getDatabasePath(\"test\").getParentFile().getAbsolutePath();\n        matchTextWithViewTag(\n                \"TAG_GET_DATABASE_PATH_FOO\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_foo\"\n        );\n        matchTextWithViewTag(\n                \"TAG_GET_DATABASE_PATH_BAR\",\n                hostDatabasePath + \"/\" + EXPECT_NAME + \"_bar\"\n        );\n        matchTextWithViewTag(\n                \"TAG_GET_DATABASE_ABSOLUTE_PATH_FOO_BAR\",\n                \"/foo/bar\"\n        );\n    }\n\n    @Test\n    public void testDatabaseList() {\n        String dbName = EXPECT_NAME + \"_bar\";\n        matchSubstringWithViewTag(\n                \"TAG_DATABASE_LIST\",\n                dbName\n        );\n    }\n\n    private void assertTextAndFileExist(String viewTag, String text) {\n        matchTextWithViewTag(viewTag, text);\n        Assert.assertTrue(\"文件应该存在\", new File(text).exists());\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/TargetFragmentTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class TargetFragmentTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.TargetFragmentTestActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void setTargetFragment() {\n        matchTextWithViewTag(\"tagOfTarget\", \"fragB\");\n        matchTextWithViewTag(\"targetRequestCode\", \"47\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ThemeTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.LargeTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\n@LargeTest\npublic class ThemeTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.context.TestThemeActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testApplicationTheme() {\n        matchTextWithViewTag(\"ApplicationThemeName\", \"android:style/Theme.NoTitleBar\");\n    }\n\n    @Test\n    public void testActivityTheme() {\n        matchTextWithViewTag(\"ActivityThemeName\", \"com.tencent.shadow.test.hostapp:style/TestPluginTheme\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/ViewIdTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class ViewIdTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.view.TestViewIdActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testAllIdEquals() {\n        matchTextWithViewTag(\"isSame\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/application_info/ApplicationInfoTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.application_info;\n\nimport org.junit.Ignore;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Suite;\n\n@RunWith(Suite.class)\n@Ignore(\"避免自动化全量测试时重复这些测试\")\n@Suite.SuiteClasses({ContextGetApplicationInfoTest.class,\n        PackageManagerGetSelfApplicationInfoTest.class,\n        PackageManagerGetOtherInstalledApplicationInfoTest.class})\npublic class ApplicationInfoTest {\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/application_info/CommonApplicationInfoTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.application_info;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\n\nimport com.tencent.shadow.test.cases.plugin_main.PluginMainAppTest;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport org.junit.Test;\n\nabstract class CommonApplicationInfoTest extends PluginMainAppTest {\n    private static final String PLUGIN_APK_FILENAME = \"test-plugin-general-cases-plugin-debug.apk\";\n\n    protected abstract String getTag();\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.TestGetApplicationInfoActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testSourceDir() throws Exception {\n        matchSubstringWithViewTag(\"TAG_sourceDir_\" + getTag(), PLUGIN_APK_FILENAME);\n    }\n\n    @Test\n    public void testNativeLibraryDir() throws Exception {\n        matchSubstringWithViewTag(\"TAG_nativeLibraryDir_\" + getTag(), TestManager.uuid + \"_lib\");\n    }\n\n    @Test\n    public abstract void testMetaData();\n\n    @Test\n    public void testClassName() throws Exception {\n        matchSubstringWithViewTag(\"TAG_className_\" + getTag(),\n                \"com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication\");\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/application_info/ContextGetApplicationInfoTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.application_info;\n\n/**\n * 在插件中通过Context.getApplicationInfo()获得到的ApplicationInfo测试。\n *\n * @author shifujun\n */\npublic class ContextGetApplicationInfoTest extends CommonApplicationInfoTest {\n\n    @Override\n    protected String getTag() {\n        return \"Context\";\n    }\n\n    @Override\n    public void testMetaData() {\n        matchTextWithViewTag(\"TAG_metaData_\" + getTag(), \"\");\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/application_info/PackageManagerGetOtherInstalledApplicationInfoTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.application_info;\n\nimport static android.content.pm.PackageManager.GET_META_DATA;\n\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\n\nimport androidx.test.core.app.ApplicationProvider;\n\n/**\n * 在插件中通过PackageManager.getApplicationInfo()获取其他正常安装应用的ApplicationInfo测试。\n *\n * @author shifujun\n */\npublic class PackageManagerGetOtherInstalledApplicationInfoTest extends CommonApplicationInfoTest {\n\n    @Override\n    protected String getTag() {\n        return \"PackageManagerGetOtherInstalled\";\n    }\n\n    private ApplicationInfo getApplicationInfoFromHost() throws PackageManager.NameNotFoundException {\n        PackageManager packageManager = ApplicationProvider.getApplicationContext().getPackageManager();\n        return packageManager.getApplicationInfo(\"android\", GET_META_DATA);\n    }\n\n    @Override\n    public void testSourceDir() throws Exception {\n        ApplicationInfo applicationInfoFromHost = getApplicationInfoFromHost();\n        matchTextWithViewTag(\"TAG_sourceDir_\" + getTag(), applicationInfoFromHost.sourceDir);\n    }\n\n    @Override\n    public void testNativeLibraryDir() throws Exception {\n        ApplicationInfo applicationInfoFromHost = getApplicationInfoFromHost();\n        matchTextWithViewTag(\"TAG_nativeLibraryDir_\" + getTag(), applicationInfoFromHost.nativeLibraryDir);\n    }\n\n    @Override\n    public void testMetaData() {\n        matchTextWithViewTag(\"TAG_metaData_\" + getTag(), \"\");\n    }\n\n    @Override\n    public void testClassName() throws Exception {\n        ApplicationInfo applicationInfoFromHost = getApplicationInfoFromHost();\n        String className = applicationInfoFromHost.className;\n        if (className == null) {\n            className = \"\";\n        }\n        matchTextWithViewTag(\"TAG_className_\" + getTag(),\n                className);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/application_info/PackageManagerGetSelfApplicationInfoTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.application_info;\n\n/**\n * 在插件中通过PackageManager.getApplicationInfo()获取插件自己的ApplicationInfo测试。\n *\n * @author shifujun\n */\npublic class PackageManagerGetSelfApplicationInfoTest extends CommonApplicationInfoTest {\n\n    @Override\n    protected String getTag() {\n        return \"PackageManagerGetSelf\";\n    }\n\n    @Override\n    public void testMetaData() {\n        matchSubstringWithViewTag(\"TAG_metaData_\" + getTag(), \"test_value\");\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/dialog_support/AlertDialogOwnerActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main.dialog_support;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport com.tencent.shadow.test.cases.plugin_main.PluginMainAppTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class AlertDialogOwnerActivityTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.dialog.TestAlertDialogActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testOwnerActivityIsThis() {\n        matchTextWithViewTag(\"ownerActivityIsThis\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/dialog_support/DialogOwnerActivityTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main.dialog_support;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport com.tencent.shadow.test.cases.plugin_main.PluginMainAppTest;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n\n@RunWith(AndroidJUnit4.class)\npublic class DialogOwnerActivityTest extends PluginMainAppTest {\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.dialog.TestDialogActivity\"\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testOwnerActivityIsThis() {\n        matchTextWithViewTag(\"ownerActivityIsThis\", Boolean.toString(true));\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/CommonFragmentSupportTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\nimport android.content.Intent;\nimport android.os.Build;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\n\nimport com.tencent.shadow.test.cases.plugin_main.PluginMainAppTest;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Assume;\nimport org.junit.Test;\n\nabstract class CommonFragmentSupportTest extends PluginMainAppTest {\n\n    abstract protected String getActivityName();\n\n    abstract protected String expectMsg();\n\n    abstract protected String fragmentType();\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName()\n        );\n        pluginIntent.putExtra(\"FragmentType\", fragmentType());\n        return pluginIntent;\n    }\n\n    @Test\n    public void setArguments() {\n        matchTextWithViewTag(\"TestFragmentTextView\", expectMsg());\n    }\n\n    @Test\n    public void fragmentStartActivity() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"fragmentStartActivity\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"finish_button\", \"finish\");\n    }\n\n    @Test\n    public void attachContext() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);\n        matchTextWithViewTag(\"AttachContextView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n\n    @Test\n    public void attachActivity() {\n        matchTextWithViewTag(\"AttachActivityView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n\n    @Test\n    public void getActivity() {\n        matchTextWithViewTag(\"GetActivityView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n\n    @Test\n    public void getContext() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);\n        matchTextWithViewTag(\"GetContextView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n\n    @Test\n    public void getHost() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);\n        matchTextWithViewTag(\"GetHostView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/FragmentSupportTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\nimport org.junit.Ignore;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Suite;\n\n@RunWith(Suite.class)\n@Ignore(\"避免自动化全量测试时重复这些测试\")\n@Suite.SuiteClasses({\n        ProgrammaticallyAddNormalFragmentTest.class,\n        ProgrammaticallyAddSubFragmentTest.class,\n        ProgrammaticallyAddBaseFragmentTest.class,\n        ProgrammaticallyAddDialogFragmentTest.class,\n        ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.class,\n        ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.class,\n        XmlAddNormalFragmentTest.class,\n        XmlAddSubFragmentTest.class,\n        XmlAddBaseFragmentTest.class\n})\npublic class FragmentSupportTest {\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddBaseFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddBaseFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestBaseFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddDialogFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddDialogFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestDialogFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\nabstract class ProgrammaticallyAddFragmentTest extends CommonFragmentSupportTest {\n    @Override\n    protected String getActivityName() {\n        return \"ProgrammaticallyAddFragmentActivity\";\n    }\n\n    @Override\n    protected String expectMsg() {\n        return \"addFragmentProgrammatically\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddNormalFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddNormalFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestNormalFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"OnlyOverrideActivityMethodBaseFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"OnlyOverrideContextMethodBaseFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/ProgrammaticallyAddSubFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class ProgrammaticallyAddSubFragmentTest extends ProgrammaticallyAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestSubFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddBaseFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class XmlAddBaseFragmentTest extends XmlAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestBaseFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\nimport android.os.Build;\n\nimport org.junit.Assume;\nimport org.junit.Test;\n\nabstract class XmlAddFragmentTest extends CommonFragmentSupportTest {\n    @Override\n    protected String getActivityName() {\n        return \"XmlAddFragmentActivity\";\n    }\n\n    @Override\n    protected String expectMsg() {\n        return \"addFragmentWithXml\";\n    }\n\n    @Test\n    public void inflateContext() {\n        Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);\n        matchTextWithViewTag(\"InflateContextView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n\n    @Test\n    public void inflateActivity() {\n        matchTextWithViewTag(\"InflateActivityView\",\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.\" + getActivityName());\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddNormalFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class XmlAddNormalFragmentTest extends XmlAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestNormalFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_main/fragment_support/XmlAddSubFragmentTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_main.fragment_support;\n\npublic class XmlAddSubFragmentTest extends XmlAddFragmentTest {\n    @Override\n    protected String fragmentType() {\n        return \"TestSubFragment\";\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_service/PluginIntentServiceConnectionTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_service;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * 测试通过bindPluginService方式绑定插件Service时，\n * PluginServiceConnection收到回调是否正常。\n */\n@RunWith(AndroidJUnit4.class)\npublic class PluginIntentServiceConnectionTest extends PluginServiceAppTest {\n    private static final String ServiceClassName =\n            \"com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host.SystemExitIntentService\";\n\n    private String packageName;\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                ServiceClassName\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testOnServiceConnected() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"BIND_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceConnected\");\n        matchTextWithViewTag(\"PACKAGE_VIEW_TAG\", packageName);\n        matchTextWithViewTag(\"CLASS_VIEW_TAG\", ServiceClassName);\n\n        //结束Service，避免影响其他用例\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"STOP_BUTTON_TAG\"))).perform(ViewActions.click());\n    }\n\n    @Test\n    public void onServiceDisconnected() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"BIND_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceConnected\");\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"STOP_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceDisconnected\");\n        matchTextWithViewTag(\"PACKAGE_VIEW_TAG\", packageName);\n        matchTextWithViewTag(\"CLASS_VIEW_TAG\", ServiceClassName);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_service/PluginServiceAppTest.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.cases.plugin_service;\n\nimport android.app.Activity;\n\nimport com.tencent.shadow.test.PluginTest;\nimport com.tencent.shadow.test.dynamic.host.BindPluginServiceActivity;\nimport com.tencent.shadow.test.lib.constant.Constant;\n\npublic abstract class PluginServiceAppTest extends PluginTest {\n\n    /**\n     * 要启动的插件的PartKey\n     */\n    @Override\n    protected String getPartKey() {\n        return Constant.PART_KEY_PLUGIN_SERVICE_FOR_HOST;\n    }\n\n    @Override\n    protected Class<? extends Activity> getJumpActivityClass() {\n        return BindPluginServiceActivity.class;\n    }\n\n    @Override\n    protected int getFromId() {\n        return Constant.FROM_ID_BIND_SERVICE;\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/cases/plugin_service/PluginServiceConnectionTest.java",
    "content": "package com.tencent.shadow.test.cases.plugin_service;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/**\n * 测试通过bindPluginService方式绑定插件Service时，\n * PluginServiceConnection收到回调是否正常。\n */\n@RunWith(AndroidJUnit4.class)\npublic class PluginServiceConnectionTest extends PluginServiceAppTest {\n    private static final String ServiceClassName =\n            \"com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host.SystemExitService\";\n\n    private String packageName;\n\n    @Override\n    protected Intent getLaunchIntent() {\n        Intent pluginIntent = new Intent();\n        packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        pluginIntent.setClassName(\n                packageName,\n                ServiceClassName\n        );\n        return pluginIntent;\n    }\n\n    @Test\n    public void testOnServiceConnected() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"BIND_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceConnected\");\n        matchTextWithViewTag(\"PACKAGE_VIEW_TAG\", packageName);\n        matchTextWithViewTag(\"CLASS_VIEW_TAG\", ServiceClassName);\n\n        //结束Service，避免影响其他用例\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"STOP_BUTTON_TAG\"))).perform(ViewActions.click());\n    }\n\n    @Test\n    public void testUnbindService() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"BIND_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceConnected\");\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"UNBIND_BUTTON_TAG\"))).perform(ViewActions.click());\n\n        // 测试Service在onUnbind时收到的Intent是否还是bind时传入的\n        matchTextWithViewTag(\"MAGIC_NUMBER_MATCH_TAG\", Boolean.toString(true));\n\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceDisconnected\");\n        matchTextWithViewTag(\"PACKAGE_VIEW_TAG\", packageName);\n        matchTextWithViewTag(\"CLASS_VIEW_TAG\", ServiceClassName);\n    }\n\n    @Test\n    public void onServiceDisconnected() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"BIND_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceConnected\");\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"STOP_BUTTON_TAG\"))).perform(ViewActions.click());\n        matchTextWithViewTag(\"STATUS_VIEW_TAG\", \"onServiceDisconnected\");\n        matchTextWithViewTag(\"PACKAGE_VIEW_TAG\", packageName);\n        matchTextWithViewTag(\"CLASS_VIEW_TAG\", ServiceClassName);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/repeat/Repeat.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.repeat;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.ANNOTATION_TYPE;\nimport static java.lang.annotation.ElementType.METHOD;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target({METHOD, ANNOTATION_TYPE})\npublic @interface Repeat {\n    int value() default 1;\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/androidTest/java/com/tencent/shadow/test/repeat/RepeatRule.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.repeat;\n\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\npublic class RepeatRule implements TestRule {\n\n    private static class RepeatStatement extends Statement {\n        private final Statement statement;\n        private final int repeat;\n\n        public RepeatStatement(Statement statement, int repeat) {\n            this.statement = statement;\n            this.repeat = repeat;\n        }\n\n        @Override\n        public void evaluate() throws Throwable {\n            for (int i = 0; i < repeat; i++) {\n                statement.evaluate();\n            }\n        }\n\n    }\n\n    @Override\n    public Statement apply(Statement statement, Description description) {\n        Statement result = statement;\n        Repeat repeat = description.getAnnotation(Repeat.class);\n        if (repeat != null) {\n            int times = repeat.value();\n            result = new RepeatStatement(statement, times);\n        }\n        return result;\n    }\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.test.dynamic.host\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"Shadow Dyanamic测试宿主App\"\n        android:name=\".HostApplication\"\n        android:theme=\"@android:style/Theme.DeviceDefault\"\n        android:icon=\"@drawable/ic_launcher\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity\n            android:exported=\"true\"\n            android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <provider\n            android:authorities=\"${applicationId}.contentprovider.authority.dynamic\"\n            android:name=\"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\"\n            android:grantUriPermissions=\"true\" />\n\n        <service android:name=\".PluginProcessPPS\" />\n        <service\n            android:name=\".PluginServiceProcessPPS\"\n            android:process=\".plugin_service\" />\n\n        <activity\n            android:name=\".PluginLoadActivity\"\n            android:launchMode=\"standard\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\" />\n\n        <activity android:name=\".JumpToPluginActivity\" />\n\n        <activity android:name=\".BindPluginServiceActivity\" />\n\n        <!--dynamic activity注册\n          注意configChanges需要全注册\n          theme需要注册成透明\n          -->\n        <activity\n            android:name=\"com.tencent.shadow.test.dynamic.runtime.container.PluginDefaultProxyActivity\"\n            android:launchMode=\"standard\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleInstance1ProxyActivity\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\" />\n\n        <activity\n            android:name=\"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleTask1ProxyActivity\"\n            android:launchMode=\"singleTask\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar.Fullscreen\" />\n        <!--dynamic activity注册 end -->\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/AndroidLogLoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.util.Log;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class AndroidLogLoggerFactory implements ILoggerFactory {\n\n    private static final int LOG_LEVEL_TRACE = 5;\n    private static final int LOG_LEVEL_DEBUG = 4;\n    private static final int LOG_LEVEL_INFO = 3;\n    private static final int LOG_LEVEL_WARN = 2;\n    private static final int LOG_LEVEL_ERROR = 1;\n\n    private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory();\n\n    public static ILoggerFactory getInstance() {\n        return sInstance;\n    }\n\n    final private ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<String, Logger>();\n\n    public Logger getLogger(String name) {\n        Logger simpleLogger = loggerMap.get(name);\n        if (simpleLogger != null) {\n            return simpleLogger;\n        } else {\n            Logger newInstance = new IVLogger(name);\n            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);\n            return oldInstance == null ? newInstance : oldInstance;\n        }\n    }\n\n    class IVLogger implements Logger {\n        private String name;\n\n        IVLogger(String name) {\n            this.name = name;\n        }\n\n        @Override\n        public String getName() {\n            return name;\n        }\n\n        private void log(int level, String message, Throwable t) {\n            final String tag = String.valueOf(name);\n\n            switch (level) {\n                case LOG_LEVEL_TRACE:\n                case LOG_LEVEL_DEBUG:\n                    if (t == null)\n                        Log.d(tag, message);\n                    else\n                        Log.d(tag, message, t);\n                    break;\n                case LOG_LEVEL_INFO:\n                    if (t == null)\n                        Log.i(tag, message);\n                    else\n                        Log.i(tag, message, t);\n                    break;\n                case LOG_LEVEL_WARN:\n                    if (t == null)\n                        Log.w(tag, message);\n                    else\n                        Log.w(tag, message, t);\n                    break;\n                case LOG_LEVEL_ERROR:\n                    if (t == null)\n                        Log.e(tag, message);\n                    else\n                        Log.e(tag, message, t);\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return true;\n        }\n\n        @Override\n        public void trace(String msg) {\n            log(LOG_LEVEL_TRACE, msg, null);\n        }\n\n        @Override\n        public void trace(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_TRACE, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void trace(String msg, Throwable throwable) {\n            log(LOG_LEVEL_TRACE, msg, throwable);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return true;\n        }\n\n        @Override\n        public void debug(String msg) {\n            log(LOG_LEVEL_DEBUG, msg, null);\n        }\n\n        @Override\n        public void debug(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_DEBUG, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void debug(String msg, Throwable throwable) {\n            log(LOG_LEVEL_DEBUG, msg, throwable);\n        }\n\n        @Override\n        public boolean isInfoEnabled() {\n            return true;\n        }\n\n        @Override\n        public void info(String msg) {\n            log(LOG_LEVEL_INFO, msg, null);\n        }\n\n        @Override\n        public void info(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_INFO, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void info(String msg, Throwable throwable) {\n            log(LOG_LEVEL_INFO, msg, throwable);\n        }\n\n        @Override\n        public boolean isWarnEnabled() {\n            return true;\n        }\n\n        @Override\n        public void warn(String msg) {\n            log(LOG_LEVEL_WARN, msg, null);\n        }\n\n        @Override\n        public void warn(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_WARN, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void warn(String msg, Throwable throwable) {\n            log(LOG_LEVEL_WARN, msg, throwable);\n        }\n\n        @Override\n        public boolean isErrorEnabled() {\n            return true;\n        }\n\n        @Override\n        public void error(String msg) {\n            log(LOG_LEVEL_ERROR, msg, null);\n        }\n\n        @Override\n        public void error(String format, Object o) {\n            FormattingTuple tuple = MessageFormatter.format(format, o);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object o, Object o1) {\n            FormattingTuple tuple = MessageFormatter.format(format, o, o1);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String format, Object... objects) {\n            FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects);\n            log(LOG_LEVEL_ERROR, tuple.getMessage(), null);\n        }\n\n        @Override\n        public void error(String msg, Throwable throwable) {\n            log(LOG_LEVEL_ERROR, msg, throwable);\n        }\n    }\n}\n\nclass FormattingTuple {\n\n    static public FormattingTuple NULL = new FormattingTuple(null);\n\n    private String message;\n    private Throwable throwable;\n    private Object[] argArray;\n\n    public FormattingTuple(String message) {\n        this(message, null, null);\n    }\n\n    public FormattingTuple(String message, Object[] argArray, Throwable throwable) {\n        this.message = message;\n        this.throwable = throwable;\n        this.argArray = argArray;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public Object[] getArgArray() {\n        return argArray;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n}\n\nfinal class MessageFormatter {\n    static final char DELIM_START = '{';\n    static final char DELIM_STOP = '}';\n    static final String DELIM_STR = \"{}\";\n    private static final char ESCAPE_CHAR = '\\\\';\n\n    /**\n     * Performs single argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}.&quot;, &quot;there&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi there.\".\n     * <p>\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg            The argument to be substituted in place of the formatting anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(String messagePattern, Object arg) {\n        return arrayFormat(messagePattern, new Object[]{arg});\n    }\n\n    /**\n     * Performs a two argument substitution for the 'messagePattern' passed as\n     * parameter.\n     * <p>\n     * For example,\n     *\n     * <pre>\n     * MessageFormatter.format(&quot;Hi {}. My name is {}.&quot;, &quot;Alice&quot;, &quot;Bob&quot;);\n     * </pre>\n     * <p>\n     * will return the string \"Hi Alice. My name is Bob.\".\n     *\n     * @param messagePattern The message pattern which will be parsed and formatted\n     * @param arg1           The argument to be substituted in place of the first formatting\n     *                       anchor\n     * @param arg2           The argument to be substituted in place of the second formatting\n     *                       anchor\n     * @return The formatted message\n     */\n    final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) {\n        return arrayFormat(messagePattern, new Object[]{arg1, arg2});\n    }\n\n\n    static final Throwable getThrowableCandidate(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            return null;\n        }\n\n        final Object lastEntry = argArray[argArray.length - 1];\n        if (lastEntry instanceof Throwable) {\n            return (Throwable) lastEntry;\n        }\n        return null;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {\n        Throwable throwableCandidate = getThrowableCandidate(argArray);\n        Object[] args = argArray;\n        if (throwableCandidate != null) {\n            args = trimmedCopy(argArray);\n        }\n        return arrayFormat(messagePattern, args, throwableCandidate);\n    }\n\n    private static Object[] trimmedCopy(Object[] argArray) {\n        if (argArray == null || argArray.length == 0) {\n            throw new IllegalStateException(\"non-sensical empty or null argument array\");\n        }\n        final int trimemdLen = argArray.length - 1;\n        Object[] trimmed = new Object[trimemdLen];\n        System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);\n        return trimmed;\n    }\n\n    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {\n\n        if (messagePattern == null) {\n            return new FormattingTuple(null, argArray, throwable);\n        }\n\n        if (argArray == null) {\n            return new FormattingTuple(messagePattern);\n        }\n\n        int i = 0;\n        int j;\n        // use string builder for better multicore performance\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n\n        int L;\n        for (L = 0; L < argArray.length; L++) {\n\n            j = messagePattern.indexOf(DELIM_STR, i);\n\n            if (j == -1) {\n                // no more variables\n                if (i == 0) { // this is a simple string\n                    return new FormattingTuple(messagePattern, argArray, throwable);\n                } else { // add the tail string which contains no variables and return\n                    // the result.\n                    sbuf.append(messagePattern, i, messagePattern.length());\n                    return new FormattingTuple(sbuf.toString(), argArray, throwable);\n                }\n            } else {\n                if (isEscapedDelimeter(messagePattern, j)) {\n                    if (!isDoubleEscaped(messagePattern, j)) {\n                        L--; // DELIM_START was escaped, thus should not be incremented\n                        sbuf.append(messagePattern, i, j - 1);\n                        sbuf.append(DELIM_START);\n                        i = j + 1;\n                    } else {\n                        // The escape character preceding the delimiter start is\n                        // itself escaped: \"abc x:\\\\{}\"\n                        // we have to consume one backward slash\n                        sbuf.append(messagePattern, i, j - 1);\n                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                        i = j + 2;\n                    }\n                } else {\n                    // normal case\n                    sbuf.append(messagePattern, i, j);\n                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());\n                    i = j + 2;\n                }\n            }\n        }\n        // append the characters following the last {} pair.\n        sbuf.append(messagePattern, i, messagePattern.length());\n        return new FormattingTuple(sbuf.toString(), argArray, throwable);\n    }\n\n    final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {\n\n        if (delimeterStartIndex == 0) {\n            return false;\n        }\n        char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);\n        if (potentialEscape == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {\n        if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // special treatment of array values was suggested by 'lizongbo'\n    private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {\n        if (o == null) {\n            sbuf.append(\"null\");\n            return;\n        }\n        if (!o.getClass().isArray()) {\n            safeObjectAppend(sbuf, o);\n        } else {\n            // check for primitive array types because they\n            // unfortunately cannot be cast to Object[]\n            if (o instanceof boolean[]) {\n                booleanArrayAppend(sbuf, (boolean[]) o);\n            } else if (o instanceof byte[]) {\n                byteArrayAppend(sbuf, (byte[]) o);\n            } else if (o instanceof char[]) {\n                charArrayAppend(sbuf, (char[]) o);\n            } else if (o instanceof short[]) {\n                shortArrayAppend(sbuf, (short[]) o);\n            } else if (o instanceof int[]) {\n                intArrayAppend(sbuf, (int[]) o);\n            } else if (o instanceof long[]) {\n                longArrayAppend(sbuf, (long[]) o);\n            } else if (o instanceof float[]) {\n                floatArrayAppend(sbuf, (float[]) o);\n            } else if (o instanceof double[]) {\n                doubleArrayAppend(sbuf, (double[]) o);\n            } else {\n                objectArrayAppend(sbuf, (Object[]) o, seenMap);\n            }\n        }\n    }\n\n    private static void safeObjectAppend(StringBuilder sbuf, Object o) {\n        try {\n            String oAsString = o.toString();\n            sbuf.append(oAsString);\n        } catch (Throwable t) {\n            sbuf.append(\"[FAILED toString()]\");\n        }\n\n    }\n\n    private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {\n        sbuf.append('[');\n        if (!seenMap.containsKey(a)) {\n            seenMap.put(a, null);\n            final int len = a.length;\n            for (int i = 0; i < len; i++) {\n                deeplyAppendParameter(sbuf, a[i], seenMap);\n                if (i != len - 1)\n                    sbuf.append(\", \");\n            }\n            // allow repeats in siblings\n            seenMap.remove(a);\n        } else {\n            sbuf.append(\"...\");\n        }\n        sbuf.append(']');\n    }\n\n    private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void charArrayAppend(StringBuilder sbuf, char[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void shortArrayAppend(StringBuilder sbuf, short[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void intArrayAppend(StringBuilder sbuf, int[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void longArrayAppend(StringBuilder sbuf, long[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void floatArrayAppend(StringBuilder sbuf, float[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n    private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {\n        sbuf.append('[');\n        final int len = a.length;\n        for (int i = 0; i < len; i++) {\n            sbuf.append(a[i]);\n            if (i != len - 1)\n                sbuf.append(\", \");\n        }\n        sbuf.append(']');\n    }\n\n}\n\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/BindPluginServiceActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.SimpleIdlingResource;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\npublic class BindPluginServiceActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_jump_to_plugin);\n        TestManager.sBindPluginServiceActivityContentView = findViewById(R.id.root);\n    }\n\n    public void jump(View view) {\n        HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);\n\n        Bundle bundle = new Bundle();\n        Intent intent = getIntent();\n        bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());\n        bundle.putString(Constant.KEY_PLUGIN_PART_KEY, intent.getStringExtra(Constant.KEY_PLUGIN_PART_KEY));\n        bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, intent.getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));\n        bundle.putBundle(Constant.KEY_EXTRAS, intent.getBundleExtra(Constant.KEY_EXTRAS));\n\n        int fromId = intent.getIntExtra(Constant.KEY_FROM_ID, Constant.FROM_ID_NOOP);\n\n        final SimpleIdlingResource idlingResource = HostApplication.getApp().mIdlingResource;\n        idlingResource.setIdleState(false);\n        HostApplication.getApp().getPluginManager()\n                .enter(this, fromId, bundle, new EnterCallback() {\n                    @Override\n                    public void onShowLoadingView(View view) {\n\n                    }\n\n                    @Override\n                    public void onCloseLoadingView() {\n                        idlingResource.setIdleState(true);\n                    }\n\n                    @Override\n                    public void onEnterComplete() {\n\n                    }\n                });\n\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/HostApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport static android.os.Process.myPid;\n\nimport android.app.ActivityManager;\nimport android.app.Application;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.StrictMode;\nimport android.webkit.WebView;\n\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.dynamic.host.DynamicRuntime;\nimport com.tencent.shadow.dynamic.host.PluginManager;\nimport com.tencent.shadow.test.dynamic.host.manager.Shadow;\n\nimport java.io.File;\n\npublic class HostApplication extends Application {\n    private static HostApplication sApp;\n\n    private PluginManager mPluginManager;\n\n    final public SimpleIdlingResourceImpl mIdlingResource = new SimpleIdlingResourceImpl();\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sApp = this;\n\n        detectNonSdkApiUsageOnAndroidP();\n\n        LoggerFactory.setILoggerFactory(new AndroidLogLoggerFactory());\n\n        if (isProcess(this, \":plugin\")) {\n            //在全动态架构中，Activity组件没有打包在宿主而是位于被动态加载的runtime，\n            //为了防止插件crash后，系统自动恢复crash前的Activity组件，此时由于没有加载runtime而发生classNotFound异常，导致二次crash\n            //因此这里恢复加载上一次的runtime\n            DynamicRuntime.recoveryRuntime(this);\n        }\n\n        if (isProcess(this, getPackageName())) {\n            PluginHelper.getInstance().init(this);\n        }\n\n\n        //Using WebView from more than one process at once with the same data directory is not supported.\n        //https://crbug.com/558377\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            WebView.setDataDirectorySuffix(Application.getProcessName());\n        }\n    }\n\n    private static void detectNonSdkApiUsageOnAndroidP() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        boolean isRunningEspressoTest;\n        try {\n            Class.forName(\"androidx.test.espresso.Espresso\");\n            isRunningEspressoTest = true;\n        } catch (Exception ignored) {\n            isRunningEspressoTest = false;\n        }\n        if (isRunningEspressoTest) {\n            return;\n        }\n        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();\n        builder.detectNonSdkApiUsage();\n        StrictMode.setVmPolicy(builder.build());\n    }\n\n    public static HostApplication getApp() {\n        return sApp;\n    }\n\n    public void loadPluginManager(File apk) {\n        if (mPluginManager == null) {\n            mPluginManager = Shadow.getPluginManager(apk);\n        }\n    }\n\n    public PluginManager getPluginManager() {\n        return mPluginManager;\n    }\n\n    private static boolean isProcess(Context context, String processName) {\n        String currentProcName = \"\";\n        ActivityManager manager =\n                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);\n        for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {\n            if (processInfo.pid == myPid()) {\n                currentProcName = processInfo.processName;\n                break;\n            }\n        }\n\n        return currentProcName.endsWith(processName);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/JumpToPluginActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.SimpleIdlingResource;\n\npublic class JumpToPluginActivity extends Activity {\n\n    final private Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {\n        @Override\n        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n\n        }\n\n        @Override\n        public void onActivityStarted(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityResumed(Activity activity) {\n            if (isPluginContainerActivity(activity)) {\n                getApplication().unregisterActivityLifecycleCallbacks(lifecycleCallbacks);\n                setIdlingResourceTrue();\n            }\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityStopped(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n\n        }\n\n        @Override\n        public void onActivityDestroyed(Activity activity) {\n\n        }\n\n        private boolean isPluginContainerActivity(Activity activity) {\n            Class<?> superclass = activity.getClass().getSuperclass();\n\n            final String superclassName;\n            if (superclass != null) {\n                superclassName = superclass.getName();\n            } else {\n                superclassName = \"\";\n            }\n            return \"com.tencent.shadow.core.runtime.container.PluginContainerActivity\".equals(superclassName);\n        }\n\n        private void setIdlingResourceTrue() {\n            final SimpleIdlingResource idlingResource = HostApplication.getApp().mIdlingResource;\n            idlingResource.setIdleState(true);\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_jump_to_plugin);\n\n        getApplication().registerActivityLifecycleCallbacks(lifecycleCallbacks);\n    }\n\n    @Override\n    protected void onDestroy() {\n        getApplication().unregisterActivityLifecycleCallbacks(lifecycleCallbacks);\n        super.onDestroy();\n    }\n\n    public void jump(View view) {\n        HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);\n\n        Bundle bundle = new Bundle();\n        Intent intent = getIntent();\n        bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());\n        bundle.putString(Constant.KEY_PLUGIN_PART_KEY, intent.getStringExtra(Constant.KEY_PLUGIN_PART_KEY));\n        bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, intent.getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));\n        bundle.putBundle(Constant.KEY_EXTRAS, intent.getBundleExtra(Constant.KEY_EXTRAS));\n\n        int fromId = intent.getIntExtra(Constant.KEY_FROM_ID, Constant.FROM_ID_NOOP);\n\n        final SimpleIdlingResource idlingResource = HostApplication.getApp().mIdlingResource;\n        idlingResource.setIdleState(false);\n        HostApplication.getApp().getPluginManager()\n                .enter(this, fromId, bundle, new EnterCallback() {\n                    @Override\n                    public void onShowLoadingView(View view) {\n\n                    }\n\n                    @Override\n                    public void onCloseLoadingView() {\n                    }\n\n                    @Override\n                    public void onEnterComplete() {\n\n                    }\n                });\n\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/MainActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.ArrayAdapter;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.Spinner;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.custom_view.TestViewConstructorCacheView;\n\n\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setTheme(R.style.TestHostTheme);\n\n        LinearLayout rootView = new LinearLayout(this);\n        rootView.setOrientation(LinearLayout.VERTICAL);\n\n        TextView infoTextView = new TextView(this);\n        infoTextView.setText(R.string.main_activity_info);\n        rootView.addView(infoTextView);\n\n        final Spinner partKeySpinner = new Spinner(this);\n        ArrayAdapter<String> partKeysAdapter = new ArrayAdapter<>(this, R.layout.part_key_adapter);\n        partKeysAdapter.addAll(\n                Constant.PART_KEY_PLUGIN_MAIN_APP\n        );\n        partKeySpinner.setAdapter(partKeysAdapter);\n\n        rootView.addView(partKeySpinner);\n\n        Button startPluginButton = new Button(this);\n        startPluginButton.setText(R.string.start_plugin);\n        startPluginButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String partKey = (String) partKeySpinner.getSelectedItem();\n                Intent intent = new Intent(MainActivity.this, PluginLoadActivity.class);\n                intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);\n                switch (partKey) {\n                    case Constant.PART_KEY_PLUGIN_MAIN_APP:\n                        intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.TestApplicationActivity\");\n                        break;\n                }\n                startActivity(intent);\n            }\n        });\n        rootView.addView(startPluginButton);\n\n        rootView.addView(new TestViewConstructorCacheView(this));\n\n        setContentView(rootView);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/PluginHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.content.Context;\n\nimport org.apache.commons.io.FileUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class PluginHelper {\n\n    /**\n     * 动态加载的插件管理apk\n     */\n    public final static String sPluginManagerName = \"pluginmanager.apk\";\n\n    /**\n     * 动态加载的插件包，里面包含以下几个部分，插件apk，插件框架apk（loader apk和runtime apk）, apk信息配置关系json文件\n     */\n    public final static String sPluginZip = BuildConfig.DEBUG ? \"plugin-debug.zip\" : \"plugin-release.zip\";\n\n    public File pluginManagerFile;\n\n    public File pluginZipFile;\n\n    public ExecutorService singlePool = Executors.newSingleThreadExecutor();\n\n    private Context mContext;\n\n    private static PluginHelper sInstance = new PluginHelper();\n\n    public static PluginHelper getInstance() {\n        return sInstance;\n    }\n\n    private PluginHelper() {\n    }\n\n    public void init(Context context) {\n        pluginManagerFile = new File(context.getFilesDir(), sPluginManagerName);\n        pluginZipFile = new File(context.getFilesDir(), sPluginZip);\n\n        mContext = context.getApplicationContext();\n\n        singlePool.execute(new Runnable() {\n            @Override\n            public void run() {\n                preparePlugin();\n            }\n        });\n\n    }\n\n    private void preparePlugin() {\n        try {\n            InputStream is = mContext.getAssets().open(sPluginManagerName);\n\n            //noinspection ResultOfMethodCallIgnored\n            pluginManagerFile.setWritable(true);\n\n            FileUtils.copyInputStreamToFile(is, pluginManagerFile);\n\n            InputStream zip = mContext.getAssets().open(sPluginZip);\n            FileUtils.copyInputStreamToFile(zip, pluginZipFile);\n\n        } catch (IOException e) {\n            throw new RuntimeException(\"从assets中复制apk出错\", e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/PluginLoadActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.lib.constant.Constant;\n\n\npublic class PluginLoadActivity extends Activity {\n\n    private ViewGroup mViewGroup;\n\n    private Handler mHandler = new Handler();\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_load);\n\n        mViewGroup = findViewById(R.id.container);\n\n        startPlugin();\n    }\n\n\n    public void startPlugin() {\n\n        PluginHelper.getInstance().singlePool.execute(new Runnable() {\n            @Override\n            public void run() {\n                HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);\n\n                Bundle bundle = new Bundle();\n                bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());\n                bundle.putString(Constant.KEY_PLUGIN_PART_KEY, getIntent().getStringExtra(Constant.KEY_PLUGIN_PART_KEY));\n                bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));\n\n                HostApplication.getApp().getPluginManager()\n                        .enter(PluginLoadActivity.this, Constant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() {\n                            @Override\n                            public void onShowLoadingView(final View view) {\n                                mHandler.post(new Runnable() {\n                                    @Override\n                                    public void run() {\n                                        mViewGroup.addView(view);\n                                    }\n                                });\n                            }\n\n                            @Override\n                            public void onCloseLoadingView() {\n                                finish();\n                            }\n\n                            @Override\n                            public void onEnterComplete() {\n\n                            }\n                        });\n            }\n        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        mViewGroup.removeAllViews();\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/PluginProcessPPS.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\n\npublic class PluginProcessPPS extends PluginProcessService {\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/PluginServiceProcessPPS.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport com.tencent.shadow.dynamic.host.PluginProcessService;\n\npublic class PluginServiceProcessPPS extends PluginProcessService {\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/SimpleIdlingResourceImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host;\n\nimport androidx.test.espresso.IdlingResource;\n\nimport com.tencent.shadow.test.lib.test_manager.SimpleIdlingResource;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A very simple implementation of {@link IdlingResource}.\n * <p>\n * Consider using CountingIdlingResource from espresso-contrib package if you use this class from\n * multiple threads or need to keep a count of pending operations.\n */\n\npublic class SimpleIdlingResourceImpl implements IdlingResource, SimpleIdlingResource {\n\n    private volatile IdlingResource.ResourceCallback mCallback;\n\n    // Idleness is controlled with this boolean.\n    private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);\n\n    @Override\n    public String getName() {\n        return this.getClass().getName();\n    }\n\n    @Override\n    public boolean isIdleNow() {\n        return mIsIdleNow.get();\n    }\n\n    @Override\n    public void registerIdleTransitionCallback(ResourceCallback callback) {\n        mCallback = callback;\n    }\n\n    /**\n     * Sets the new idle state, if isIdleNow is true, it pings the {@link ResourceCallback}.\n     *\n     * @param isIdleNow false if there are pending operations, true if idle.\n     */\n    @Override\n    public void setIdleState(boolean isIdleNow) {\n        mIsIdleNow.set(isIdleNow);\n        if (isIdleNow && mCallback != null) {\n            mCallback.onTransitionToIdle();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/manager/FixedPathPmUpdater.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host.manager;\n\nimport android.os.Build;\n\nimport com.tencent.shadow.dynamic.host.PluginManagerUpdater;\n\nimport java.io.File;\nimport java.util.concurrent.Future;\n\npublic class FixedPathPmUpdater implements PluginManagerUpdater {\n\n    final private File apk;\n\n    FixedPathPmUpdater(File apk) {\n        this.apk = apk;\n\n        //在API 33以上的系统上，禁止动态加载文件可写入，满足系统安全限制\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {\n            //noinspection ResultOfMethodCallIgnored\n            apk.setWritable(false);\n        }\n    }\n\n\n    @Override\n    public boolean wasUpdating() {\n        return false;\n    }\n\n    @Override\n    public Future<File> update() {\n        return null;\n    }\n\n    @Override\n    public File getLatest() {\n        return apk;\n    }\n\n    @Override\n    public Future<Boolean> isAvailable(final File file) {\n        return null;\n    }\n}"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/java/com/tencent/shadow/test/dynamic/host/manager/Shadow.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.host.manager;\n\nimport com.tencent.shadow.dynamic.host.DynamicPluginManager;\nimport com.tencent.shadow.dynamic.host.PluginManager;\n\nimport java.io.File;\n\npublic class Shadow {\n\n    public static PluginManager getPluginManager(File apk) {\n        final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk);\n        File tempPm = fixedPathPmUpdater.getLatest();\n        if (tempPm != null) {\n            return new DynamicPluginManager(fixedPathPmUpdater);\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/res/layout/activity_jump_to_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/root\"\n    android:orientation=\"vertical\">\n\n    <Button\n        android:id=\"@+id/jump\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"jump\"\n        android:text=\"跳转\" />\n</LinearLayout>"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/res/layout/activity_load.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/container\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n</FrameLayout>"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/res/layout/part_key_adapter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:textColor=\"@android:color/black\" />"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <string name=\"main_activity_info\">\n        这是一个全动态的测试程序，插件管理（test-dynamic-manager）,\n        插件框架（test-dynamic-loader及test-dynamic-runtime），\n        以及插件本身,都是动态加载的\n    </string>\n\n    <string name=\"start_plugin\">\n        启动插件\n    </string>\n</resources>"
  },
  {
    "path": "projects/test/dynamic/host/test-dynamic-host/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"TestHostTheme\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\" />\n\n</resources>"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.dynamic:dynamic-manager'\n    implementation 'com.tencent.shadow.core:manager'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader'\n    implementation project(':constant')\n\n    compileOnly 'com.tencent.shadow.core:common'\n    compileOnly 'com.tencent.shadow.dynamic:dynamic-host'\n    compileOnly project(':test-manager')\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.test.dynamic.manager\" />\n\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/dynamic/impl/ManagerFactoryImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.dynamic.host.ManagerFactory;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.test.dynamic.manager.TestDynamicPluginManager;\n\n/**\n * 此类包名及类名固定\n */\npublic final class ManagerFactoryImpl implements ManagerFactory {\n    @Override\n    public PluginManagerImpl buildManager(Context context) {\n        return new TestDynamicPluginManager(context);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.impl;\n\n/**\n * 此类包名及类名固定\n * classLoader的白名单\n * PluginManager可以加载宿主中位于白名单内的类\n */\npublic interface WhiteList {\n    String[] sWhiteList = new String[]\n            {\n                    \"com.tencent.host.shadow\",\n                    \"com.tencent.shadow.test.lib.constant\",\n                    \"com.tencent.shadow.test.lib.test_manager\",\n            };\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/UiUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.ScrollView;\nimport android.widget.TextView;\n\nimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;\n\nfinal public class UiUtil {\n    private static ViewGroup makeItemViewGroup(Context viewContext) {\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        return linearLayout;\n    }\n\n    private static ScrollView wrapScrollView(View view) {\n        ScrollView scrollView = new ScrollView(view.getContext());\n        scrollView.addView(view);\n        return scrollView;\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    public static ViewGroup makeItemView(Context viewContext, String labelText, String viewTag) {\n        TextView label = new TextView(viewContext);\n        label.setText(labelText + \":\");\n        label.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n\n        TextView value = new TextView(viewContext);\n        value.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n        value.setTag(viewTag);\n\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        linearLayout.setPadding(0, 10, 0, 10);\n\n        linearLayout.addView(label);\n        linearLayout.addView(value);\n        return linearLayout;\n    }\n\n    public static void setItemValue(ViewGroup viewGroupContainsItem, String viewTag, String value) {\n        TextView textView = viewGroupContainsItem.findViewWithTag(viewTag);\n        textView.setText(value);\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        setItemValue(itemView, viewTag, value);\n        return itemView;\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            AsyncGetValue asyncGetValue\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        asyncGetValue.getValue(new AsyncGetValueCallback() {\n            @Override\n            public void onGotValue(String value) {\n                setItemValue(itemView, viewTag, value);\n            }\n        });\n        return itemView;\n    }\n\n    interface AsyncGetValue {\n        void getValue(AsyncGetValueCallback callback);\n    }\n\n    interface AsyncGetValueCallback {\n        void onGotValue(String value);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/cases/PluginIntentServiceConnectionTestCase.java",
    "content": "package com.tencent.shadow.test.cases;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Parcel;\nimport android.os.RemoteException;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\n\nimport com.tencent.shadow.dynamic.loader.PluginLoader;\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection;\nimport com.tencent.shadow.test.UiUtil;\nimport com.tencent.shadow.test.lib.test_manager.SimpleIdlingResource;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport java.util.concurrent.CountDownLatch;\n\npublic class PluginIntentServiceConnectionTestCase {\n\n    private static final String STATUS_VIEW_TAG = \"STATUS_VIEW_TAG\";\n    private static final String PACKAGE_VIEW_TAG = \"PACKAGE_VIEW_TAG\";\n    private static final String CLASS_VIEW_TAG = \"CLASS_VIEW_TAG\";\n    private static final String BIND_BUTTON_TAG = \"BIND_BUTTON_TAG\";\n    private static final String STOP_BUTTON_TAG = \"STOP_BUTTON_TAG\";\n\n    final private ViewGroup viewGroup;\n    private IBinder service;\n    final private PluginLoader pluginLoader;\n    final private Intent pluginIntent;\n    final private SimpleIdlingResource idlingResource;\n    final private Handler uiHandler;\n\n    public PluginIntentServiceConnectionTestCase(PluginLoader pluginLoader, Intent pluginIntent) {\n        this.pluginLoader = pluginLoader;\n        this.pluginIntent = pluginIntent;\n        viewGroup = (ViewGroup) TestManager.sBindPluginServiceActivityContentView;\n        idlingResource = TestManager.TheSimpleIdlingResource;\n        uiHandler = new Handler(Looper.getMainLooper());\n    }\n\n    public void prepareUi() {\n        final Context context = viewGroup.getContext();\n        final CountDownLatch waitUiThread = new CountDownLatch(1);\n\n        uiHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ServiceConnection Callback\",\n                                STATUS_VIEW_TAG,\n                                \"\"\n                        )\n                );\n\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ComponentName.getPackageName()\",\n                                PACKAGE_VIEW_TAG,\n                                \"\"\n                        )\n                );\n\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ComponentName.getClassName()\",\n                                CLASS_VIEW_TAG,\n                                \"\"\n                        )\n                );\n                Button bindService = new Button(context);\n                bindService.setTag(BIND_BUTTON_TAG);\n                bindService.setText(\"bindService\");\n                bindService.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        v.setEnabled(false);\n                        bindService();\n                    }\n                });\n\n                Button stopService = new Button(context);\n                stopService.setTag(STOP_BUTTON_TAG);\n                stopService.setText(\"stopService\");\n                stopService.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        v.setEnabled(false);\n                        stopService();\n                    }\n                });\n\n                viewGroup.addView(bindService);\n                viewGroup.addView(stopService);\n\n                waitUiThread.countDown();\n            }\n        });\n\n        try {\n            waitUiThread.await();\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void bindService() {\n        idlingResource.setIdleState(false);\n        try {\n            pluginLoader.bindPluginService(pluginIntent, serviceConnection, Context.BIND_AUTO_CREATE);\n        } catch (RemoteException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void stopService() {\n        idlingResource.setIdleState(false);\n        try {\n            //随便发什么过去都表示杀进程\n            service.transact(0, Parcel.obtain(), Parcel.obtain(), 0);\n        } catch (RemoteException ignored) {\n        }\n    }\n\n    final private PluginServiceConnection serviceConnection = new PluginServiceConnection() {\n        @Override\n        public void onServiceConnected(final ComponentName name, IBinder service) {\n            PluginIntentServiceConnectionTestCase.this.service = service;\n            uiHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    UiUtil.setItemValue(viewGroup, STATUS_VIEW_TAG, \"onServiceConnected\");\n                    UiUtil.setItemValue(viewGroup, PACKAGE_VIEW_TAG, name.getPackageName());\n                    UiUtil.setItemValue(viewGroup, CLASS_VIEW_TAG, name.getClassName());\n                    idlingResource.setIdleState(true);\n                }\n            });\n        }\n\n        @Override\n        public void onServiceDisconnected(final ComponentName name) {\n            uiHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    UiUtil.setItemValue(viewGroup, STATUS_VIEW_TAG, \"onServiceDisconnected\");\n                    UiUtil.setItemValue(viewGroup, PACKAGE_VIEW_TAG, name.getPackageName());\n                    UiUtil.setItemValue(viewGroup, CLASS_VIEW_TAG, name.getClassName());\n                    idlingResource.setIdleState(true);\n                }\n            });\n        }\n    };\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/cases/PluginServiceConnectionTestCase.java",
    "content": "package com.tencent.shadow.test.cases;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Parcel;\nimport android.os.RemoteException;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\n\nimport com.tencent.shadow.dynamic.loader.PluginLoader;\nimport com.tencent.shadow.dynamic.loader.PluginServiceConnection;\nimport com.tencent.shadow.test.UiUtil;\nimport com.tencent.shadow.test.lib.test_manager.SimpleIdlingResource;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.util.concurrent.CountDownLatch;\n\npublic class PluginServiceConnectionTestCase {\n\n    private static final String STATUS_VIEW_TAG = \"STATUS_VIEW_TAG\";\n    private static final String PACKAGE_VIEW_TAG = \"PACKAGE_VIEW_TAG\";\n    private static final String CLASS_VIEW_TAG = \"CLASS_VIEW_TAG\";\n    private static final String BIND_BUTTON_TAG = \"BIND_BUTTON_TAG\";\n    private static final String MAGIC_NUMBER_MATCH_TAG = \"MAGIC_NUMBER_MATCH_TAG\";\n    private static final String UNBIND_BUTTON_TAG = \"UNBIND_BUTTON_TAG\";\n    private static final String STOP_BUTTON_TAG = \"STOP_BUTTON_TAG\";\n\n    final private ViewGroup viewGroup;\n    private IBinder service;\n    final private PluginLoader pluginLoader;\n    final private Intent pluginIntent;\n    final private SimpleIdlingResource idlingResource;\n    final private Handler uiHandler;\n    private long bindServiceMagicNumber;\n\n    public PluginServiceConnectionTestCase(PluginLoader pluginLoader, Intent pluginIntent) {\n        this.pluginLoader = pluginLoader;\n        this.pluginIntent = pluginIntent;\n        viewGroup = (ViewGroup) TestManager.sBindPluginServiceActivityContentView;\n        idlingResource = TestManager.TheSimpleIdlingResource;\n        uiHandler = new Handler(Looper.getMainLooper());\n    }\n\n    public void prepareUi() {\n        final Context context = viewGroup.getContext();\n        final CountDownLatch waitUiThread = new CountDownLatch(1);\n\n        uiHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ServiceConnection Callback\",\n                                STATUS_VIEW_TAG,\n                                \"\"\n                        )\n                );\n\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ComponentName.getPackageName()\",\n                                PACKAGE_VIEW_TAG,\n                                \"\"\n                        )\n                );\n\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                context,\n                                \"ComponentName.getClassName()\",\n                                CLASS_VIEW_TAG,\n                                \"\"\n                        )\n                );\n\n                viewGroup.addView(\n                        UiUtil.makeItem(\n                                viewGroup.getContext(),\n                                \"magic_number_matched\",\n                                MAGIC_NUMBER_MATCH_TAG,\n                                Boolean.toString(false)\n                        )\n                );\n\n                Button bindService = new Button(context);\n                bindService.setTag(BIND_BUTTON_TAG);\n                bindService.setText(\"bindService\");\n                bindService.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        v.setEnabled(false);\n                        bindService();\n                    }\n                });\n\n                Button unbindService = new Button(context);\n                unbindService.setTag(UNBIND_BUTTON_TAG);\n                unbindService.setText(\"unbindService\");\n                unbindService.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        v.setEnabled(false);\n                        unbindService();\n                    }\n                });\n\n                Button stopService = new Button(context);\n                stopService.setTag(STOP_BUTTON_TAG);\n                stopService.setText(\"stopService\");\n                stopService.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        v.setEnabled(false);\n                        stopService();\n                    }\n                });\n\n                viewGroup.addView(bindService);\n                viewGroup.addView(unbindService);\n                viewGroup.addView(stopService);\n\n                waitUiThread.countDown();\n            }\n        });\n\n        try {\n            waitUiThread.await();\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void bindService() {\n        idlingResource.setIdleState(false);\n        try {\n            // 在bind service的intent中放入一个参数，以便测试Service收到onUnbind时能获得bind时的intent。\n            bindServiceMagicNumber = System.currentTimeMillis();\n            pluginIntent.putExtra(\"magic_number\", bindServiceMagicNumber);\n            pluginLoader.bindPluginService(pluginIntent, serviceConnection, Context.BIND_AUTO_CREATE);\n        } catch (RemoteException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void unbindService() {\n        idlingResource.setIdleState(false);\n        try {\n            pluginLoader.unbindService(serviceConnection);\n        } catch (RemoteException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void stopService() {\n        idlingResource.setIdleState(false);\n        try {\n            //随便发什么过去都表示杀进程\n            service.transact(0, Parcel.obtain(), Parcel.obtain(), 0);\n        } catch (RemoteException ignored) {\n        }\n    }\n\n    final private PluginServiceConnection serviceConnection = new PluginServiceConnection() {\n        @Override\n        public void onServiceConnected(final ComponentName name, IBinder service) {\n            PluginServiceConnectionTestCase.this.service = service;\n            uiHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    UiUtil.setItemValue(viewGroup, STATUS_VIEW_TAG, \"onServiceConnected\");\n                    UiUtil.setItemValue(viewGroup, PACKAGE_VIEW_TAG, name.getPackageName());\n                    UiUtil.setItemValue(viewGroup, CLASS_VIEW_TAG, name.getClassName());\n                    idlingResource.setIdleState(true);\n                }\n            });\n        }\n\n        @Override\n        public void onServiceDisconnected(final ComponentName name) {\n            uiHandler.post(new Runnable() {\n                @Override\n                public void run() {\n                    UiUtil.setItemValue(viewGroup, STATUS_VIEW_TAG, \"onServiceDisconnected\");\n                    UiUtil.setItemValue(viewGroup, PACKAGE_VIEW_TAG, name.getPackageName());\n                    UiUtil.setItemValue(viewGroup, CLASS_VIEW_TAG, name.getClassName());\n                    long actualMagicNumber = readSystemExitServiceOnUnbindWriteOutMagicNumber();\n                    UiUtil.setItemValue(viewGroup,\n                            MAGIC_NUMBER_MATCH_TAG,\n                            Boolean.toString(bindServiceMagicNumber == actualMagicNumber));\n                    idlingResource.setIdleState(true);\n                }\n\n                private long readSystemExitServiceOnUnbindWriteOutMagicNumber() {\n                    File pluginFilesDir = new File(viewGroup.getContext().getFilesDir(),\n                            \"ShadowPlugin_plugin-service-for-host\");\n                    File magicNumberOutputFile = new File(pluginFilesDir,\n                            \"SystemExitService.onUnbind\");\n\n                    try (BufferedReader br = new BufferedReader(new FileReader(magicNumberOutputFile))) {\n                        String s = br.readLine();\n                        return Long.parseLong(s);\n                    } catch (Exception ignored) {\n                        return -1;//与bindServiceMagicNumber默认值0不同。\n                    }\n                }\n            });\n        }\n    };\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/dynamic/manager/ActivityTestDynamicPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.manager;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n\npublic class ActivityTestDynamicPluginManager extends FastPluginManager {\n\n    private ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n    private Context mCurrentContext;\n\n    public ActivityTestDynamicPluginManager(Context context) {\n        super(context);\n        mCurrentContext = context;\n    }\n\n    /**\n     * @return PluginManager实现的别名，用于区分不同PluginManager实现的数据存储路径\n     */\n    @Override\n    protected String getName() {\n        return \"test-dynamic-manager\";\n    }\n\n    /**\n     * @return 宿主中注册的PluginProcessService实现的类名\n     */\n    @Override\n    protected String getPluginProcessServiceName() {\n        return \"com.tencent.shadow.test.dynamic.host.PluginProcessPPS\";\n    }\n\n    @Override\n    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {\n        if (fromId == Constant.FROM_ID_NOOP) {\n            //do nothing.\n        } else if (fromId == Constant.FROM_ID_START_ACTIVITY) {\n            onStartActivity(context, bundle, callback);\n        } else {\n            throw new IllegalArgumentException(\"不认识的fromId==\" + fromId);\n        }\n    }\n\n    private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) {\n        final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH);\n        final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY);\n        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);\n        if (className == null) {\n            throw new NullPointerException(\"className == null\");\n        }\n        final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);\n\n        if (callback != null) {\n            final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);\n            callback.onShowLoadingView(view);\n        }\n\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);\n\n                    TestManager.uuid = installedPlugin.UUID;\n\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(\n                            context.getPackageName(),\n                            className\n                    );\n                    if (extras != null) {\n                        pluginIntent.replaceExtras(extras);\n                    }\n\n                    startPluginActivity(context, installedPlugin, partKey, pluginIntent);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n                if (callback != null) {\n                    callback.onCloseLoadingView();\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/dynamic/manager/FastPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.manager;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.RemoteException;\nimport android.util.Pair;\n\nimport com.tencent.shadow.core.common.Logger;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.core.manager.installplugin.InstalledType;\nimport com.tencent.shadow.core.manager.installplugin.PluginConfig;\nimport com.tencent.shadow.dynamic.host.FailedException;\nimport com.tencent.shadow.dynamic.manager.PluginManagerThatUseDynamicLoader;\n\nimport org.json.JSONException;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\npublic abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {\n\n    private static final Logger mLogger = LoggerFactory.getLogger(FastPluginManager.class);\n\n    private ExecutorService mFixedPool = Executors.newFixedThreadPool(4);\n\n    public FastPluginManager(Context context) {\n        super(context);\n    }\n\n\n    public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {\n        final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash);\n        final String uuid = pluginConfig.UUID;\n        List<Future> futures = new LinkedList<>();\n        List<Future<Pair<String, String>>> extractSoFutures = new LinkedList<>();\n        if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) {\n            Future odexRuntime = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME,\n                            pluginConfig.runTime.file);\n                    return null;\n                }\n            });\n            futures.add(odexRuntime);\n            Future odexLoader = mFixedPool.submit(new Callable() {\n                @Override\n                public Object call() throws Exception {\n                    oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER,\n                            pluginConfig.pluginLoader.file);\n                    return null;\n                }\n            });\n            futures.add(odexLoader);\n        }\n        for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : pluginConfig.plugins.entrySet()) {\n            final String partKey = plugin.getKey();\n            final File apkFile = plugin.getValue().file;\n            Future<Pair<String, String>> extractSo = mFixedPool.submit(() -> extractSo(uuid, partKey, apkFile));\n            futures.add(extractSo);\n            extractSoFutures.add(extractSo);\n            if (odex) {\n                Future odexPlugin = mFixedPool.submit(new Callable() {\n                    @Override\n                    public Object call() throws Exception {\n                        oDexPlugin(uuid, partKey, apkFile);\n                        return null;\n                    }\n                });\n                futures.add(odexPlugin);\n            }\n        }\n\n        for (Future future : futures) {\n            future.get();\n        }\n        Map<String, String> soDirMap = new HashMap<>();\n        for (Future<Pair<String, String>> future : extractSoFutures) {\n            Pair<String, String> pair = future.get();\n            soDirMap.put(pair.first, pair.second);\n        }\n        onInstallCompleted(pluginConfig, soDirMap);\n\n        return getInstalledPlugins(1).get(0);\n    }\n\n\n    public void startPluginActivity(Context context, InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {\n        Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent);\n        if (!(context instanceof Activity)) {\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n        context.startActivity(intent);\n    }\n\n    public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {\n        loadPlugin(installedPlugin.UUID, partKey);\n        return mPluginLoader.convertActivityIntent(pluginIntent);\n    }\n\n    private void loadPluginLoaderAndRuntime(String uuid) throws RemoteException, TimeoutException, FailedException {\n        if (mPpsController == null) {\n            bindPluginProcessService(getPluginProcessServiceName());\n            waitServiceConnected(10, TimeUnit.SECONDS);\n        }\n        loadRunTime(uuid);\n        loadPluginLoader(uuid);\n    }\n\n    protected void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {\n        loadPluginLoaderAndRuntime(uuid);\n        Map map = mPluginLoader.getLoadedPlugin();\n        if (!map.containsKey(partKey)) {\n            mPluginLoader.loadPlugin(partKey);\n        }\n        Boolean isCall = (Boolean) map.get(partKey);\n        if (isCall == null || !isCall) {\n            mPluginLoader.callApplicationOnCreate(partKey);\n        }\n    }\n\n\n    protected abstract String getPluginProcessServiceName();\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/dynamic/manager/ReinstallPluginTestDynamicPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.manager;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n\npublic class ReinstallPluginTestDynamicPluginManager extends FastPluginManager {\n\n    private ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n    private Context mCurrentContext;\n\n    public ReinstallPluginTestDynamicPluginManager(Context context) {\n        super(context);\n        mCurrentContext = context;\n    }\n\n    /**\n     * @return PluginManager实现的别名，用于区分不同PluginManager实现的数据存储路径\n     */\n    @Override\n    protected String getName() {\n        return \"reinstall-plugin-test-dynamic-manager\";\n    }\n\n    /**\n     * @return 宿主中注册的PluginProcessService实现的类名\n     */\n    @Override\n    protected String getPluginProcessServiceName() {\n        return \"com.tencent.shadow.test.dynamic.host.PluginProcessPPS\";\n    }\n\n    @Override\n    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {\n        final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH);\n        final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY);\n        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);\n        if (className == null) {\n            throw new NullPointerException(\"className == null\");\n        }\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    // 安装后卸载再安装\n                    InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);\n                    deleteInstalledPlugin(installedPlugin.UUID);\n                    installedPlugin = installPlugin(pluginZipPath, null, true);\n\n                    TestManager.uuid = installedPlugin.UUID;\n\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(\n                            context.getPackageName(),\n                            className\n                    );\n\n                    startPluginActivity(context, installedPlugin, partKey, pluginIntent);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/dynamic/manager/ServiceTestDynamicPluginManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.manager;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport com.tencent.shadow.core.manager.installplugin.InstalledPlugin;\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.test.cases.PluginIntentServiceConnectionTestCase;\nimport com.tencent.shadow.test.cases.PluginServiceConnectionTestCase;\nimport com.tencent.shadow.test.lib.constant.Constant;\nimport com.tencent.shadow.test.lib.test_manager.TestManager;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n/**\n * 针对plugin-service-for-host插件\n */\npublic class ServiceTestDynamicPluginManager extends FastPluginManager {\n\n    private ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n    private Context mCurrentContext;\n\n    public ServiceTestDynamicPluginManager(Context context) {\n        super(context);\n        mCurrentContext = context;\n    }\n\n    /**\n     * @return PluginManager实现的别名，用于区分不同PluginManager实现的数据存储路径\n     */\n    @Override\n    protected String getName() {\n        return \"service-test-dynamic-manager\";\n    }\n\n    /**\n     * @return 宿主中注册的PluginProcessService实现的类名\n     */\n    @Override\n    protected String getPluginProcessServiceName() {\n        return \"com.tencent.shadow.test.dynamic.host.PluginServiceProcessPPS\";\n    }\n\n    @Override\n    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {\n        if (fromId == Constant.FROM_ID_BIND_SERVICE) {\n            onBindService(context, bundle, callback);\n        } else {\n            throw new IllegalArgumentException(\"不认识的fromId==\" + fromId);\n        }\n    }\n\n    private void doCase(Intent pluginIntent) throws InterruptedException {\n        String className = pluginIntent.getComponent().getClassName();\n        switch (className) {\n            case \"com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host.SystemExitService\":\n                PluginServiceConnectionTestCase systemExitServiceCase = new PluginServiceConnectionTestCase(mPluginLoader, pluginIntent);\n                systemExitServiceCase.prepareUi();\n                break;\n            case \"com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host.SystemExitIntentService\":\n                PluginIntentServiceConnectionTestCase systemExitIntentService = new PluginIntentServiceConnectionTestCase(mPluginLoader, pluginIntent);\n                systemExitIntentService.prepareUi();\n                break;\n            default:\n                throw new IllegalArgumentException(className + \"没有对应的PluginServiceConnection\");\n        }\n    }\n\n    private void onBindService(final Context context, Bundle bundle, final EnterCallback callback) {\n        final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH);\n        final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY);\n        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);\n        if (className == null) {\n            throw new NullPointerException(\"className == null\");\n        }\n        final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);\n\n        if (callback != null) {\n            final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);\n            callback.onShowLoadingView(view);\n        }\n\n        executorService.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);\n\n                    TestManager.uuid = installedPlugin.UUID;\n\n                    loadPlugin(installedPlugin.UUID, partKey);\n\n                    Intent pluginIntent = new Intent();\n                    pluginIntent.setClassName(\n                            context.getPackageName(),\n                            className\n                    );\n                    if (extras != null) {\n                        pluginIntent.replaceExtras(extras);\n                    }\n\n                    doCase(pluginIntent);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n                if (callback != null) {\n                    callback.onCloseLoadingView();\n                }\n            }\n        });\n\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/java/com/tencent/shadow/test/dynamic/manager/TestDynamicPluginManager.java",
    "content": "package com.tencent.shadow.test.dynamic.manager;\n\nimport android.content.Context;\nimport android.os.Bundle;\n\nimport com.tencent.shadow.dynamic.host.EnterCallback;\nimport com.tencent.shadow.dynamic.host.PluginManagerImpl;\nimport com.tencent.shadow.test.lib.constant.Constant;\n\nfinal public class TestDynamicPluginManager implements PluginManagerImpl {\n    final private ActivityTestDynamicPluginManager activityPluginManager;\n    final private ServiceTestDynamicPluginManager serviceTestDynamicPluginManager;\n    final private ReinstallPluginTestDynamicPluginManager reinstallPluginTestDynamicPluginManager;\n\n    public TestDynamicPluginManager(Context context) {\n        this.activityPluginManager = new ActivityTestDynamicPluginManager(context);\n        this.serviceTestDynamicPluginManager = new ServiceTestDynamicPluginManager(context);\n        this.reinstallPluginTestDynamicPluginManager = new ReinstallPluginTestDynamicPluginManager(context);\n    }\n\n    @Override\n    public void onCreate(Bundle bundle) {\n        activityPluginManager.onCreate(bundle);\n        serviceTestDynamicPluginManager.onCreate(bundle);\n        reinstallPluginTestDynamicPluginManager.onCreate(bundle);\n    }\n\n    @Override\n    public void onSaveInstanceState(Bundle bundle) {\n        activityPluginManager.onSaveInstanceState(bundle);\n        serviceTestDynamicPluginManager.onSaveInstanceState(bundle);\n        reinstallPluginTestDynamicPluginManager.onSaveInstanceState(bundle);\n    }\n\n    @Override\n    public void onDestroy() {\n        activityPluginManager.onDestroy();\n        serviceTestDynamicPluginManager.onDestroy();\n        reinstallPluginTestDynamicPluginManager.onDestroy();\n    }\n\n    @Override\n    public void enter(Context context, long fromId, Bundle bundle, EnterCallback callback) {\n        if (fromId == Constant.FROM_ID_BIND_SERVICE) {\n            serviceTestDynamicPluginManager.enter(context, fromId, bundle, callback);\n        } else if (fromId == Constant.FROM_ID_START_ACTIVITY) {\n            activityPluginManager.enter(context, fromId, bundle, callback);\n        } else if (fromId == Constant.FROM_ID_REINSTALL_PLUGIN) {\n            reinstallPluginTestDynamicPluginManager.enter(context, fromId, bundle, callback);\n        } else {\n            throw new RuntimeException(\"不认识的fromId==\" + fromId);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/manager/test-dynamic-manager/src/main/res/layout/activity_load_plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"#fcfcfc\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/black\"\n        android:layout_marginTop=\"20dp\"\n        android:textSize=\"30sp\"\n        android:text=\"这是一个位于dynamic-pluginmanager-apk中的view\" />\n\n    <ProgressBar\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\" />\n</LinearLayout>"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.core:loader'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader'\n    implementation 'com.tencent.shadow.dynamic:dynamic-loader-impl'\n    implementation project(':constant')\n\n    compileOnly 'com.tencent.shadow.core:runtime'\n    compileOnly 'com.tencent.shadow.core:activity-container'\n    compileOnly 'com.tencent.shadow.core:common'\n    //下面这行依赖是为了防止在proguard的时候找不到LoaderFactory接口\n    compileOnly 'com.tencent.shadow.dynamic:dynamic-host'\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n\n#kotlin一般性配置 START\n-dontwarn kotlin.**\n-keepclassmembers class **$WhenMappings {\n    <fields>;\n}\n-keepclassmembers class kotlin.Metadata {\n    public <methods>;\n}\n#kotlin一般性配置 END\n\n#kotlin优化性能 START\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);\n}\n#kotlin优化性能 END\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.dynamic.impl.**{*;}\n-keep class com.tencent.shadow.dynamic.loader.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.loader.**{*;}\n-keep class com.tencent.shadow.core.runtime.**{*;}\n\n-dontwarn  com.tencent.shadow.dynamic.host.**\n-dontwarn  com.tencent.shadow.dynamic.impl.**\n-dontwarn  com.tencent.shadow.dynamic.loader.**\n-dontwarn  com.tencent.shadow.core.common.**\n-dontwarn  com.tencent.shadow.core.loader.**\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.test.dynamic.loader\" />\n\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/src/main/java/com/tencent/shadow/dynamic/loader/impl/CoreLoaderFactoryImpl.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.dynamic.loader.impl;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.test.dynamic.loader.TestPluginLoader;\n\n/**\n * 这个类的包名类名是固定的。\n * <p>\n * 见com.tencent.shadow.dynamic.loader.impl.DynamicPluginLoader#CORE_LOADER_FACTORY_IMPL_NAME\n */\npublic class CoreLoaderFactoryImpl implements CoreLoaderFactory {\n    @Override\n    public ShadowPluginLoader build(Context hostAppContext) {\n        return new TestPluginLoader(hostAppContext);\n    }\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/src/main/java/com/tencent/shadow/test/dynamic/loader/TestComponentManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.loader;\n\nimport android.content.ComponentName;\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\npublic class TestComponentManager extends ComponentManager {\n\n    /**\n     * dynamic-runtime-apk 模块中定义的壳子Activity，需要在宿主AndroidManifest.xml注册\n     */\n    private static final String DEFAULT_ACTIVITY = \"com.tencent.shadow.test.dynamic.runtime.container.PluginDefaultProxyActivity\";\n    private static final String SINGLE_INSTANCE_ACTIVITY = \"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleInstance1ProxyActivity\";\n    private static final String SINGLE_TASK_ACTIVITY = \"com.tencent.shadow.test.dynamic.runtime.container.PluginSingleTask1ProxyActivity\";\n\n    private Context context;\n\n    public TestComponentManager(Context context) {\n        this.context = context;\n    }\n\n\n    /**\n     * 配置插件Activity 到 壳子Activity的对应关系\n     *\n     * @param pluginActivity 插件Activity\n     * @return 壳子Activity\n     */\n    @Override\n    public ComponentName onBindContainerActivity(ComponentName pluginActivity) {\n        switch (pluginActivity.getClassName()) {\n            /**\n             * 这里配置对应的对应关系\n             */\n        }\n        return new ComponentName(context, DEFAULT_ACTIVITY);\n    }\n\n    /**\n     * 配置对应宿主中预注册的壳子contentProvider的信息\n     */\n    @Override\n    public ContainerProviderInfo onBindContainerContentProvider(ComponentName pluginContentProvider) {\n        return new ContainerProviderInfo(\n                \"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\",\n                context.getPackageName() + \".contentprovider.authority.dynamic\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-loader/src/main/java/com/tencent/shadow/test/dynamic/loader/TestPluginLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.loader;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\npublic class TestPluginLoader extends ShadowPluginLoader {\n\n    private final static String TAG = \"shadow\";\n\n    private ComponentManager componentManager;\n\n    public TestPluginLoader(Context hostAppContext) {\n        super(hostAppContext);\n        componentManager = new TestComponentManager(hostAppContext);\n    }\n\n    @Override\n    public ComponentManager getComponentManager() {\n        return componentManager;\n    }\n\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n}\n\ndependencies {\n    implementation 'com.tencent.shadow.core:activity-container'\n}\n\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.core.runtime.**{*;}"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.tencent.shadow.test.dynamic.runtime\" />\n\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/src/main/java/com/tencent/shadow/test/dynamic/runtime/container/PluginDefaultProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.runtime.container;\n\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginDefaultProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/src/main/java/com/tencent/shadow/test/dynamic/runtime/container/PluginSingleInstance1ProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.runtime.container;\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginSingleInstance1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/test/dynamic/plugin/test-dynamic-runtime/src/main/java/com/tencent/shadow/test/dynamic/runtime/container/PluginSingleTask1ProxyActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.dynamic.runtime.container;\n\nimport android.annotation.SuppressLint;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\n@SuppressLint(\"Registered\")//无需注册在这个模块的Manifest中，要注册在宿主的Manifest中。\npublic class PluginSingleTask1ProxyActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.gradletasknamecache\n\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/README.md",
    "content": "## core.gradle-plugin模块的AGP各版本黑盒测试\n\n准备一个桩工程`stub-project`，通过命令行参数控制其AGP版本和Shadow版本。\n\n自动化测试脚本: `test_JDK11.sh`和`test_JDK17.sh`。其中先编译Shadow，发布到本地Maven，然后用这个Shadow版本进行测试。\n\n注意脚本会echo出执行的命令，如果遇到测试失败，可复制命令手工重新执行。\n\n`test_JDK11.sh`需要JDK 11环境，`test_JDK17.sh`需要JDK 17环境。\n\n`test_clean.sh`可以还原测试脚本对Gradle文件对改动。\n\n### 确定实际使用的AGP版本：\n\n查看`stub-project/build/intermediates/app_metadata/pluginDebug/app-metadata.properties`\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\n#distributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionUrl=https\\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4096m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.useAndroidX=true\norg.gradle.caching=false\n\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        mavenLocal()\n    }\n}\nrootProject.name = 'gradle-plugin-agp-compat-test'\nif (SetGradleVersion != 'true') {\n    include 'stub-project'\n}\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n.DS_Store\nbuild\n/captures\n.externalNativeBuild\n.gradletasknamecache"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/build.gradle",
    "content": "buildscript {\n    loadVersions:\n    {// 读取versions.properties到ext中，供项目中直接用变量引用版本号\n        def versions_properties_path = '../../../../buildScripts/gradle/versions.properties'\n        def versions = new Properties()\n        versions.load(file(versions_properties_path).newReader())\n        versions.forEach { key, stringValue ->\n            def value = stringValue?.isInteger() ? stringValue as Integer : stringValue\n            ext.set(key, value)\n        }\n    }\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n        mavenLocal()\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:$TestAGPVersion\"\n        classpath \"com.tencent.shadow.core:gradle-plugin:$ShadowVersion\"\n    }\n}\ntry {\n    plugins {\n        id 'com.android.application' version \"$TestAGPVersion\" apply true\n        id 'com.tencent.shadow.plugin' version \"$ShadowVersion\" apply true\n    }\n} catch (Exception ignored) {\n    apply plugin: 'com.android.application'\n    apply plugin: 'com.tencent.shadow.plugin'\n}\n\nallprojects {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n}\n\next.disable_shadow_transform = true\n\nandroid {\n    try {\n        namespace 'app'\n    } catch (Exception ignored) {\n        // AGP 8之前找不到namespace这个方法的，不用设置。\n    }\n\n    // 对于 3.4.0 以及以下版本，compileSdkVersion 不能为 SDK 33 。\n    // 因为 SDK 33 中的 android.car.jar 是由 JDK 11 编译的。而这些版本 AGP 中的 R8 不支持 JDK 11 。\n    if (TestAGPVersion.matches(\"3\\\\.[1234]\\\\.0\")) {\n        compileSdkVersion 32\n    } else {\n        compileSdkVersion COMPILE_SDK_VERSION\n    }\n\n    defaultConfig {\n        applicationId \"com.tencent.shadow.test.gradle.stub_project\"\n        minSdkVersion MIN_SDK_VERSION\n        targetSdkVersion TARGET_SDK_VERSION\n        versionCode VERSION_CODE\n        versionName VERSION_NAME\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled true\n            shrinkResources true\n\n            proguardFiles 'proguard-rules.pro'\n        }\n    }\n\n    // 测试插件项目存在自定义flavorDimensions\n    flavorDimensions(*flavorDimensionList, 'DimensionA', 'DimensionB')\n    productFlavors {\n        A1 {\n            dimension 'DimensionA'\n        }\n        B2 {\n            dimension 'DimensionB'\n        }\n\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n}\n\n// 创建两个假的apk，用于验证打包流程（packagePlugin）\nafterEvaluate {\n    tasks.getByPath(\":stub-project:assemblePluginA1B2Debug\").doLast {\n        File loader = project.file(\"build/outputs/apk/plugina1b2debug/loader.apk\")\n        File runtime = project.file(\"build/outputs/apk/plugina1b2debug/runtime.apk\")\n        loader.parentFile.mkdirs()\n        loader.createNewFile()\n        runtime.createNewFile()\n    }\n}\n\nshadow {\n    packagePlugin {\n        pluginTypes {\n            A1B2Debug {\n                loaderApkConfig = new Tuple2('loader.apk', ':stub-project:assemblePluginA1B2Debug')\n                runtimeApkConfig = new Tuple2('runtime.apk', ':stub-project:assemblePluginA1B2Debug')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'stub-project'\n                        partKey = 'stub-project'\n                        buildTask = ':stub-project:assemblePluginA1B2Debug'\n                        apkPath = 'stub-project/build/outputs/apk/pluginA1B2/debug/stub-project-plugin-A1-B2-debug.apk'\n                        dependsOn = ['Core', 'Base']\n                        hostWhiteList = [\"androidx.test.espresso\",\n                                         \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"]\n                    }\n                }\n            }\n        }\n\n        loaderApkProjectPath = 'stub-project'\n\n        runtimeApkProjectPath = 'stub-project'\n\n        uuid = '1234567890'\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}\n\ndependencies {\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly \"com.tencent.shadow.core:runtime:$ShadowVersion\"\n}\n\nrepositories {\n    if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n        maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n    } else {\n        google()\n        mavenCentral()\n    }\n    mavenLocal()\n}"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/proguard-rules.pro",
    "content": "-keep class PluginManifestIncludeTest"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/settings.gradle",
    "content": "rootProject.name = 'caseAGPCompat'"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"app\">\n\n    <application android:theme=\"@style/AppTheme\" />\n</manifest>\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/src/main/java/PluginManifestIncludeTest.java",
    "content": "/**\n * 引用一下PluginManifest类型，测试自动生成的\n * PluginManifest.java 是否被添加到编译源码路径中了\n */\npublic final class PluginManifestIncludeTest {\n    public static final Class DEBUG = com.tencent.shadow.core.manifest_parser.PluginManifest.class;\n}"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/stub-project/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"AppTheme\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\" />\n</resources>"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/test_JDK11.sh",
    "content": "#!/usr/bin/env bash\n\n# Shadow工程因需要支持更高版本AGP，本身也需要用更高版本AGP构建。从AGP 8开始要求用JDK 17了。\n# 但使用Shadow的工程应该可以使用JDK 11。此脚本需要在JDK 11下测试低版本AGP工程的兼容性。\n\nJAVA_MAJOR_VERSION=$(javap -verbose java.lang.Object | grep \"major version\" | cut -d \" \" -f5)\nif [[ $JAVA_MAJOR_VERSION -ne 55 ]]; then\n  echo \"需要JDK 11!\"\n  exit 1\nfi\n\nsource ./test_prepare.sh\n\n# 测试版本来源\n# AGP release页面：https://developer.android.com/studio/releases/gradle-plugin\n# AGP Maven仓库：https://mvnrepository.com/artifact/com.android.tools.build/gradle\n# Gradle下载：https://services.gradle.org/distributions/\nsetGradleVersion 7.5\ntestUnderAGPVersion 7.4.1\nsetGradleVersion 7.4\ntestUnderAGPVersion 7.3.1\nsetGradleVersion 7.3.3\ntestUnderAGPVersion 7.2.2\nsetGradleVersion 7.2\ntestUnderAGPVersion 7.1.1\nsetGradleVersion 7.0.2\ntestUnderAGPVersion 7.0.0\ntestUnderAGPVersion 4.2.0\ntestUnderAGPVersion 4.1.0\nsetGradleVersion 6.1.1\ntestUnderAGPVersion 4.0.0\nsetGradleVersion 5.6.4\ntestUnderAGPVersion 3.6.0\nsetGradleVersion 5.4.1\ntestUnderAGPVersion 3.5.0\ntestUnderAGPVersion 3.4.0\ntestUnderAGPVersion 3.3.0\ntestUnderAGPVersion 3.2.0\n\n# 3.1.0在配合aapt2使用--package-id选项时不能设置小于0x7f的值，因此不再支持\n#testUnderAGPVersion 3.1.0\n\n#更低的版本支持成本过高\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/test_JDK17.sh",
    "content": "#!/usr/bin/env bash\n\n# 从Gradle 7.5和AGP 8.0开始支持和要求使用JDK 17，所以这个脚本中的待测版本需要在JDK 17环境下测试。\n\nJAVA_MAJOR_VERSION=$(javap -verbose java.lang.Object | grep \"major version\" | cut -d \" \" -f5)\nif [[ $JAVA_MAJOR_VERSION -ne 61 ]]; then\n  echo \"需要JDK 17!\"\n  exit 1\nfi\n\nsource ./test_prepare.sh\n\n# 测试版本来源\n# AGP release页面：https://developer.android.com/studio/releases/gradle-plugin\n# AGP Maven仓库：https://mvnrepository.com/artifact/com.android.tools.build/gradle\n# Gradle下载：https://services.gradle.org/distributions/\nsetGradleVersion 8.11.1\ntestUnderAGPVersion 8.9.0\nsetGradleVersion 8.6\ntestUnderAGPVersion 8.4.0-rc02\nsetGradleVersion 8.4\ntestUnderAGPVersion 8.3.2\nsetGradleVersion 8.2.1\ntestUnderAGPVersion 8.2.0\nsetGradleVersion 8.0.2\ntestUnderAGPVersion 8.1.4\ntestUnderAGPVersion 8.0.2\nsetGradleVersion 7.5.1\ntestUnderAGPVersion 7.4.1\n"
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/test_clean.sh",
    "content": "#!/usr/bin/env bash\n\nset -eo pipefail\n\ngit restore gradle/wrapper gradlew gradlew.bat\ngit clean -fdx ."
  },
  {
    "path": "projects/test/gradle-plugin-agp-compat-test/test_prepare.sh",
    "content": "#!/usr/bin/env bash\n\nset -eo pipefail\n\npushd ../../../\n./gradlew publishToMavenLocal\nline=$(./gradlew getPublicationVersion | grep \"publicationVersion:\")\nShadowVersion=${line:19}\npopd\n\nfunction setGradleVersion() {\n  local distributionBase=https\\://mirrors.tencent.com/gradle/\n  if [ \"$DISABLE_TENCENT_MAVEN_MIRROR\" = true ] ; then\n    distributionBase=https\\://services.gradle.org/distributions/\n  fi\n\n  local GradleVersion=$1\n  echo ./gradlew -PSetGradleVersion=true wrapper --gradle-distribution-url ${distributionBase}gradle-$GradleVersion-bin.zip\n  ./gradlew -PSetGradleVersion=true wrapper --gradle-distribution-url ${distributionBase}gradle-$GradleVersion-bin.zip\n}\n\nfunction testUnderAGPVersion() {\n  local TestAGPVersion=$1\n  rm -rf stub-project/build\n\n  echo ./gradlew -PSetGradleVersion=false -PTestAGPVersion=$TestAGPVersion -PShadowVersion=$ShadowVersion :stub-project:packageA1B2DebugPlugin\n  ./gradlew -PSetGradleVersion=false -PTestAGPVersion=$TestAGPVersion -PShadowVersion=$ShadowVersion :stub-project:packageA1B2DebugPlugin\n}\n"
  },
  {
    "path": "projects/test/lib/constant/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/lib/constant/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "projects/test/lib/constant/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/test/lib/constant/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.test.lib.constant\" />\n"
  },
  {
    "path": "projects/test/lib/constant/src/main/java/com/tencent/shadow/test/lib/constant/Constant.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.lib.constant;\n\nfinal public class Constant {\n    public static final String KEY_PLUGIN_ZIP_PATH = \"pluginZipPath\";\n    public static final String KEY_ACTIVITY_CLASSNAME = \"KEY_ACTIVITY_CLASSNAME\";\n    public static final String KEY_EXTRAS = \"KEY_EXTRAS\";\n    public static final String KEY_PLUGIN_PART_KEY = \"KEY_PLUGIN_PART_KEY\";\n    public static final String KEY_FROM_ID = \"KEY_FROM_ID\";\n    public static final String PART_KEY_PLUGIN_MAIN_APP = \"test-plugin-general-cases\";\n    public static final String PART_KEY_PLUGIN_SERVICE_FOR_HOST = \"plugin-service-for-host\";\n    public static final String PART_KEY_PLUGIN_ANDROIDX = \"test-plugin-androidx-cases\";\n    public static final int FROM_ID_NOOP = 1000;\n    public static final int FROM_ID_START_ACTIVITY = 1002;\n    public static final int FROM_ID_BIND_SERVICE = 1003;\n    public static final int FROM_ID_REINSTALL_PLUGIN = 1004;\n}\n"
  },
  {
    "path": "projects/test/lib/custom-view/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/lib/custom-view/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n\n    defaultConfig {\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "projects/test/lib/custom-view/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "projects/test/lib/custom-view/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.tencent.shadow.test.lib.custom_view\" />\n"
  },
  {
    "path": "projects/test/lib/custom-view/src/main/java/com/tencent/shadow/test/lib/custom_view/TestViewConstructorCacheView.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.lib.custom_view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n/**\n * 测试同名View构造器缓存冲突的View\n */\npublic class TestViewConstructorCacheView extends View {\n    public TestViewConstructorCacheView(Context context) {\n        super(context);\n    }\n\n    public TestViewConstructorCacheView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n}\n"
  },
  {
    "path": "projects/test/lib/plugin-use-host-code-lib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/lib/plugin-use-host-code-lib/build.gradle",
    "content": "apply plugin: 'java-library'\n\ngroup 'com.tencent.shadow.test.lib.plugin-use-host-code-lib'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n}\n\nsourceCompatibility = \"7\"\ntargetCompatibility = \"7\"\n"
  },
  {
    "path": "projects/test/lib/plugin-use-host-code-lib/src/main/java/com/tencent/shadow/test/lib/plugin_use_host_code_lib/interfaces/HostTestInterface.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces;\n\npublic class HostTestInterface {\n\n    public static String getText() {\n        return \"HostTestInterface getText\";\n    }\n}\n"
  },
  {
    "path": "projects/test/lib/plugin-use-host-code-lib/src/main/java/com/tencent/shadow/test/lib/plugin_use_host_code_lib/interfaces/subpackage/Foo.java",
    "content": "package com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces.subpackage;\n\npublic final class Foo {\n}\n"
  },
  {
    "path": "projects/test/lib/plugin-use-host-code-lib/src/main/java/com/tencent/shadow/test/lib/plugin_use_host_code_lib/other/HostOtherInterface.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.lib.plugin_use_host_code_lib.other;\n\npublic class HostOtherInterface {\n\n    public static String getText() {\n        return \"HostOtherInterface getText\";\n    }\n}\n"
  },
  {
    "path": "projects/test/lib/test-manager/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/lib/test-manager/build.gradle",
    "content": "apply plugin: 'java-library'\n\ngroup 'com.tencent.shadow.test.lib.test-manager'\n\ndependencies {\n    compileOnly 'com.tencent.shadow.dynamic:dynamic-host'\n}\nsourceCompatibility = JavaVersion.VERSION_1_8\ntargetCompatibility = JavaVersion.VERSION_1_8\n"
  },
  {
    "path": "projects/test/lib/test-manager/src/main/java/com/tencent/shadow/test/lib/test_manager/SimpleIdlingResource.java",
    "content": "package com.tencent.shadow.test.lib.test_manager;\n\npublic interface SimpleIdlingResource {\n    void setIdleState(boolean isIdleNow);\n}\n"
  },
  {
    "path": "projects/test/lib/test-manager/src/main/java/com/tencent/shadow/test/lib/test_manager/TestManager.java",
    "content": "package com.tencent.shadow.test.lib.test_manager;\n\npublic class TestManager {\n    public static String uuid;\n\n    public static SimpleIdlingResource TheSimpleIdlingResource;\n\n    public static Object sBindPluginServiceActivityContentView;\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.none_dynamic.host.CustomAndroidJUnitRunner\"\n    }\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n    sourceSets {\n        debug {\n            assets.srcDir('build/generated/assets/test-plugin-general-cases/debug/')\n        }\n        release {\n            assets.srcDir('build/generated/assets/test-plugin-general-cases/release/')\n        }\n    }\n    lintOptions {\n        checkReleaseBuilds false\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation \"commons-io:commons-io:$commons_io_android_version\"//example复制apk用的.\n    implementation \"org.slf4j:slf4j-api:$slf4j_version\"\n\n    implementation 'com.tencent.shadow.core:common'\n    implementation\"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation 'com.tencent.shadow.core:loader'\n    implementation 'com.tencent.shadow.core:activity-container'\n    implementation project(':custom-view')\n    implementation \"androidx.test.espresso:espresso-idling-resource:$espresso_version\"\n    implementation project(':plugin-use-host-code-lib')\n}\n\ndef createCopyTask(projectName, buildType, name, apkName) {\n    def split = (projectName as String).split(':')\n    def moduleName = split[split.length - 1]\n    def outputFile = file(\"${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}\")\n    outputFile.getParentFile().mkdirs()\n    def inputFile = file(\"${project(\"${projectName}\").getBuildDir()}/outputs/apk/plugin/${buildType}/${moduleName}-plugin-${buildType}.apk\")\n    return tasks.create(\"copy${buildType.capitalize()}${name.capitalize()}Task\", Copy) {\n        group = 'build'\n        description = \"复制${name}到assets中.\"\n        from(inputFile.getParent()) {\n            include(inputFile.name)\n            rename { outputFile.name }\n        }\n        into(outputFile.getParent())\n\n    }.dependsOn(\"${projectName}:assemblePlugin${buildType.capitalize()}\")\n}\n\ntasks.whenTaskAdded { task ->\n    if (task.name == \"generateDebugAssets\") {\n        task.dependsOn createCopyTask(':test-plugin-general-cases', 'debug', 'test-plugin-general-cases', 'plugin.apk')\n    }\n}"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class org.slf4j.**{*;}\n-dontwarn org.slf4j.impl.**\n\n-keep class com.tencent.shadow.dynamic.host.**{*;}\n-keep class com.tencent.shadow.core.common.**{*;}\n-keep class com.tencent.shadow.core.runtime.**{*;}"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.test.none_dynamic.host\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"Shadow测试宿主App\"\n        android:name=\".HostApplication\"\n        android:theme=\"@android:style/Theme.DeviceDefault\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".DefaultContainerActivity\"\n            android:screenOrientation=\"portrait\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\" />\n\n        <activity\n            android:name=\".SingleTaskContainerActivity\"\n            android:screenOrientation=\"portrait\"\n            android:launchMode=\"singleTask\"\n            android:configChanges=\"mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection\" />\n\n\n        <provider\n            android:name=\"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\"\n            android:authorities=\"com.tencent.shadow.contentprovider.authority\"\n            android:exported=\"true\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/DefaultContainerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class DefaultContainerActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/HostApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport android.app.Application;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Parcel;\nimport android.os.StrictMode;\n\nimport com.tencent.shadow.core.common.InstalledApk;\nimport com.tencent.shadow.core.common.LoggerFactory;\nimport com.tencent.shadow.core.load_parameters.LoadParameters;\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.core.runtime.container.ContentProviderDelegateProviderHolder;\nimport com.tencent.shadow.core.runtime.container.DelegateProviderHolder;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\n\npublic class HostApplication extends Application {\n    private static Application sApp;\n\n    public final static String PART_MAIN = \"partMain\";\n\n    private static final PreparePluginApkBloc sPluginPrepareBloc\n            = new PreparePluginApkBloc(\n            \"plugin.apk\"\n    );\n\n    static {\n        detectNonSdkApiUsageOnAndroidP();\n\n        LoggerFactory.setILoggerFactory(new SLoggerFactory());\n    }\n\n    private ShadowPluginLoader mPluginLoader;\n\n    private final Map<String, InstalledApk> mPluginMap = new HashMap<>();\n\n    public void loadPlugin(final String partKey, final Runnable completeRunnable) {\n        InstalledApk installedApk = mPluginMap.get(partKey);\n        if (installedApk == null) {\n            throw new NullPointerException(\"partKey == \" + partKey);\n        }\n\n        if (mPluginLoader.getPluginParts(partKey) == null) {\n            // 插件访问宿主类的白名单\n            String[] hostWhiteList = new String[]{\n                    \"androidx.test.espresso\",//这个包添加是为了general-cases插件中可以访问测试框架的类\n                    \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"//测试插件访问宿主白名单类\n            };\n            LoadParameters loadParameters = new LoadParameters(null,\n                    partKey,\n                    null,\n                    hostWhiteList);\n\n            Parcel parcel = Parcel.obtain();\n            loadParameters.writeToParcel(parcel, 0);\n            final InstalledApk plugin = new InstalledApk(\n                    installedApk.apkFilePath,\n                    installedApk.oDexPath,\n                    installedApk.libraryPath,\n                    parcel.marshall()\n            );\n            parcel.recycle();\n\n            new AsyncTask<Void, Void, Void>() {\n                @Override\n                protected Void doInBackground(Void... voids) {\n                    ShadowPluginLoader pluginLoader = mPluginLoader;\n                    Future<?> future = null;\n                    try {\n                        future = pluginLoader.loadPlugin(plugin);\n                        future.get(10, TimeUnit.SECONDS);\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                        throw new RuntimeException(\"加载失败\", e);\n                    }\n                    return null;\n                }\n\n                @Override\n                protected void onPostExecute(Void aVoid) {\n                    super.onPostExecute(aVoid);\n                    mPluginLoader.callApplicationOnCreate(partKey);\n                    completeRunnable.run();\n                }\n            }.execute();\n        } else {\n            completeRunnable.run();\n        }\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sApp = this;\n\n        ShadowPluginLoader loader = mPluginLoader = new TestPluginLoader(getApplicationContext());\n        loader.onCreate();\n        DelegateProviderHolder.setDelegateProvider(loader.getDelegateProviderKey(), loader);\n        ContentProviderDelegateProviderHolder.setContentProviderDelegateProvider(loader);\n\n        InstalledApk installedApk = sPluginPrepareBloc.preparePlugin(this.getApplicationContext());\n        mPluginMap.put(PART_MAIN, installedApk);\n    }\n\n    private static void detectNonSdkApiUsageOnAndroidP() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        boolean isRunningEspressoTest;\n        try {\n            Class.forName(\"android.support.test.espresso.Espresso\");\n            isRunningEspressoTest = true;\n        } catch (Exception ignored) {\n            isRunningEspressoTest = false;\n        }\n        if (isRunningEspressoTest) {\n            return;\n        }\n        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();\n        builder.detectNonSdkApiUsage();\n        StrictMode.setVmPolicy(builder.build());\n    }\n\n    public static Application getApp() {\n        return sApp;\n    }\n\n    public ShadowPluginLoader getPluginLoader() {\n        return mPluginLoader;\n    }\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/MainActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport static com.tencent.shadow.test.none_dynamic.host.HostApplication.PART_MAIN;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\n\npublic class MainActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setTheme(R.style.TestHostTheme);\n        setContentView(R.layout.activity_main);\n    }\n\n    public void startPlugin(View view) {\n        final HostApplication application = (HostApplication) getApplication();\n        application.loadPlugin(PART_MAIN, new Runnable() {\n            @Override\n            public void run() {\n                Intent pluginIntent = new Intent();\n                pluginIntent.setClassName(getPackageName(), \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestListActivity\");\n                pluginIntent.putStringArrayListExtra(\"activities\", TestComponentManager.sActivities);\n                Intent intent = application.getPluginLoader().getMComponentManager().convertPluginActivityIntent(pluginIntent);\n                startActivity(intent);\n            }\n        });\n\n    }\n\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/PreparePluginApkBloc.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.common.InstalledApk;\n\nimport org.apache.commons.io.FileUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class PreparePluginApkBloc {\n    private static final Logger mLogger = LoggerFactory.getLogger(PreparePluginApkBloc.class);\n    private final String mPluginFileName;\n    private boolean mPrepared = false;\n    private InstalledApk mPreparePlugin;\n\n    public PreparePluginApkBloc(String mPluginFileName) {\n        this.mPluginFileName = mPluginFileName;\n    }\n\n    InstalledApk preparePlugin(Context context) {\n        if (mPrepared) {\n            return mPreparePlugin;\n        }\n        mPrepared = true;\n        if (mLogger.isInfoEnabled()) {\n            mLogger.info(\"preparePlugin\");\n        }\n        //宿主程序必须加载过armeabi的so,插件才可以以armeabi ABI兼容模式运行,否则在64位手机上,系统会加载插件的arm64 ABI的so。\n//        System.loadLibrary(\"encry\");//这是一个随意拿来的armeabi的so。\n\n        copyFile(context, mPluginFileName);\n\n        InstalledApk installedPlugin = installPlugin(getApkFileInDataDir(context, mPluginFileName));\n        mPreparePlugin = installedPlugin;\n        return installedPlugin;\n    }\n\n    private static void copyFile(Context context, String name) {\n        File pluginFile = getApkFileInDataDir(context, name);\n        if (pluginFile.exists()) {\n            pluginFile.delete();\n        }\n        try {\n            copyFromAssetsToData(context, name);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static void copyFromAssetsToData(Context context, String filename) throws IOException {\n        InputStream is = context.getAssets().open(filename);\n        File destination = getApkFileInDataDir(context, filename);\n        if (destination.exists()) {\n            destination.delete();\n        }\n        FileUtils.copyInputStreamToFile(is, destination);\n    }\n\n    public static File getApkFileInDataDir(Context context, String filename) {\n        return new File(context.getFilesDir() + \"/\" + filename);\n    }\n\n    private static InstalledApk installPlugin(File apk) {\n        File[] classLoaderDirs = prepareClassLoaderDirs(apk);\n        return new InstalledApk(\n                apk.getAbsolutePath(),\n                getOdexDir(classLoaderDirs).getAbsolutePath(),\n                getLibDir(classLoaderDirs).getAbsolutePath()\n        );\n    }\n\n    private static File getOdexDir(File[] classLoaderDirs) {\n        return classLoaderDirs[0];\n    }\n\n    private static File getLibDir(File[] classLoaderDirs) {\n        return classLoaderDirs[1];\n    }\n\n    private static File[] prepareClassLoaderDirs(File apk) {\n        File odexDir = new File(apk.getParent(), apk.getName() + \"_odex\");\n        File libDir = new File(apk.getParent(), apk.getName() + \"_lib\");\n        prepareDirs(odexDir, libDir);\n        return new File[]{odexDir, libDir};\n    }\n\n    private static void prepareDirs(File odexDir, File libDir) {\n        if (odexDir.exists() && !odexDir.isDirectory()) {\n            throw new RuntimeException(\"odexDir目标路径\" + odexDir.getAbsolutePath()\n                    + \"已被其他文件占用\");\n        } else if (!odexDir.exists()) {\n            boolean success = odexDir.mkdir();\n            if (!success) {\n                throw new RuntimeException(\"odexDir目标路径\" + odexDir.getAbsolutePath()\n                        + \"创建目录失败\");\n            }\n        }\n\n        if (!libDir.exists()) {\n            if (!libDir.mkdirs()) {\n                throw new RuntimeException(\"libDir目标路径\" + libDir.getAbsolutePath()\n                        + \"创建目录失败\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/SLoggerFactory.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport com.tencent.shadow.core.common.ILoggerFactory;\nimport com.tencent.shadow.core.common.Logger;\n\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class SLoggerFactory implements ILoggerFactory {\n\n    private ConcurrentHashMap<String, Logger> mLoggers = new ConcurrentHashMap<>();\n\n    @Override\n    public Logger getLogger(String s) {\n        if (mLoggers.get(s) == null) {\n            mLoggers.put(s, new SLogger(LoggerFactory.getLogger(s)));\n        }\n        return mLoggers.get(s);\n    }\n\n\n    class SLogger implements Logger {\n        private org.slf4j.Logger mLogger;\n\n        SLogger(org.slf4j.Logger logger) {\n            mLogger = logger;\n        }\n\n        @Override\n        public String getName() {\n            return mLogger.getName();\n        }\n\n        @Override\n        public boolean isTraceEnabled() {\n            return mLogger.isTraceEnabled();\n        }\n\n        @Override\n        public void trace(String msg) {\n            mLogger.trace(msg);\n        }\n\n        @Override\n        public void trace(String format, Object arg) {\n            mLogger.trace(format, arg);\n        }\n\n        @Override\n        public void trace(String format, Object arg1, Object arg2) {\n            mLogger.trace(format, arg1, arg2);\n        }\n\n        @Override\n        public void trace(String format, Object... arguments) {\n            mLogger.trace(format, arguments);\n        }\n\n        @Override\n        public void trace(String msg, Throwable t) {\n            mLogger.trace(msg, t);\n        }\n\n        @Override\n        public boolean isDebugEnabled() {\n            return mLogger.isDebugEnabled();\n        }\n\n        @Override\n        public void debug(String msg) {\n            mLogger.debug(msg);\n        }\n\n        @Override\n        public void debug(String format, Object arg) {\n            mLogger.debug(format, arg);\n        }\n\n        @Override\n        public void debug(String format, Object arg1, Object arg2) {\n            mLogger.debug(format, arg1, arg2);\n        }\n\n        @Override\n        public void debug(String format, Object... arguments) {\n            mLogger.debug(format, arguments);\n        }\n\n        @Override\n        public void debug(String msg, Throwable t) {\n            mLogger.debug(msg, t);\n        }\n\n\n        @Override\n        public boolean isInfoEnabled() {\n            return mLogger.isInfoEnabled();\n        }\n\n        @Override\n        public void info(String msg) {\n            mLogger.info(msg);\n        }\n\n        @Override\n        public void info(String format, Object arg) {\n            mLogger.info(format, arg);\n        }\n\n        @Override\n        public void info(String format, Object arg1, Object arg2) {\n            mLogger.info(format, arg1, arg2);\n        }\n\n        @Override\n        public void info(String format, Object... arguments) {\n            mLogger.info(format, arguments);\n        }\n\n        @Override\n        public void info(String msg, Throwable t) {\n            mLogger.info(msg, t);\n        }\n\n\n        @Override\n        public boolean isWarnEnabled() {\n            return mLogger.isWarnEnabled();\n        }\n\n        @Override\n        public void warn(String msg) {\n            mLogger.warn(msg);\n        }\n\n        @Override\n        public void warn(String format, Object arg) {\n            mLogger.warn(format, arg);\n        }\n\n        @Override\n        public void warn(String format, Object... arguments) {\n            mLogger.warn(format, arguments);\n        }\n\n        @Override\n        public void warn(String format, Object arg1, Object arg2) {\n            mLogger.warn(format, arg1, arg2);\n        }\n\n        @Override\n        public void warn(String msg, Throwable t) {\n            mLogger.warn(msg, t);\n        }\n\n\n        @Override\n        public boolean isErrorEnabled() {\n            return mLogger.isErrorEnabled();\n        }\n\n        @Override\n        public void error(String msg) {\n            mLogger.error(msg);\n        }\n\n        @Override\n        public void error(String format, Object arg) {\n            mLogger.error(format, arg);\n        }\n\n        @Override\n        public void error(String format, Object arg1, Object arg2) {\n            mLogger.error(format, arg1, arg2);\n        }\n\n        @Override\n        public void error(String format, Object... arguments) {\n            mLogger.error(format, arguments);\n        }\n\n        @Override\n        public void error(String msg, Throwable t) {\n            mLogger.error(msg, t);\n        }\n\n    }\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/SingleTaskContainerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport com.tencent.shadow.core.runtime.container.PluginContainerActivity;\n\npublic class SingleTaskContainerActivity extends PluginContainerActivity {\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/TestComponentManager.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport android.content.ComponentName;\n\nimport com.tencent.shadow.core.loader.infos.ContainerProviderInfo;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\n\npublic class TestComponentManager extends ComponentManager {\n    final private static ComponentName sDefaultContainer = new ComponentName(BuildConfig.APPLICATION_ID, \"com.tencent.shadow.test.none_dynamic.host.DefaultContainerActivity\");\n    final private static ComponentName sSingleTaskContainer = new ComponentName(BuildConfig.APPLICATION_ID, \"com.tencent.shadow.test.none_dynamic.host.SingleTaskContainerActivity\");\n    public static final ArrayList<String> sActivities = new ArrayList<>();\n\n    @Override\n    public ComponentName onBindContainerActivity(ComponentName componentName) {\n        if (!sActivities.contains(componentName.getClassName())) {\n            sActivities.add(componentName.getClassName());\n        }\n        if (componentName.getClassName().equals(\"com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity.TestActivityOrientation\")) {\n            return sSingleTaskContainer;\n        }\n        return sDefaultContainer;\n    }\n\n\n    @NotNull\n    @Override\n    public ContainerProviderInfo onBindContainerContentProvider(@NotNull ComponentName pluginContentProvider) {\n        return new ContainerProviderInfo(\"com.tencent.shadow.core.runtime.container.PluginContainerContentProvider\", \"com.tencent.shadow.contentprovider.authority\");\n    }\n\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/TestLoadingActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.View;\n\npublic class TestLoadingActivity extends Activity {\n\n    public static View content;\n    public static Activity activity;\n\n    public static void startActivity(Activity activity, Bundle bundle, View viewContent) {\n        Intent intent = new Intent(activity, TestLoadingActivity.class);\n        intent.putExtras(bundle);\n        content = viewContent;\n        activity.startActivity(intent);\n    }\n\n    public static void finishSelf() {\n        if (activity != null) {\n            activity.finish();\n        }\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(content);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {\n                requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);\n            }\n        }\n        activity = this;\n\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        if (requestCode == 1) {\n            for (int i = 0; i < permissions.length; i++) {\n                if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {\n                        throw new RuntimeException(\"必须赋予权限.\");\n                    }\n                }\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/java/com/tencent/shadow/test/none_dynamic/host/TestPluginLoader.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.none_dynamic.host;\n\nimport android.content.Context;\n\nimport com.tencent.shadow.core.loader.ShadowPluginLoader;\nimport com.tencent.shadow.core.loader.managers.ComponentManager;\n\nimport org.jetbrains.annotations.NotNull;\n\n\npublic class TestPluginLoader extends ShadowPluginLoader {\n\n    final private ComponentManager mCM = new TestComponentManager();\n\n    public TestPluginLoader(@NotNull Context context) {\n        super(context);\n    }\n\n    @Override\n    public ComponentManager getComponentManager() {\n        return mCM;\n    }\n\n}\n"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".MainActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\" />\n\n    <Button\n        android:id=\"@+id/startButton\"\n        android:text=\"@string/start_plugin\"\n        android:onClick=\"startPlugin\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n    <com.tencent.shadow.test.lib.custom_view.TestViewConstructorCacheView\n        android:id=\"@+id/testView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/res/layout/qq_nearby_now_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n\n    android:background=\"#ffffffff\">\n\n    <TextView\n        android:id=\"@+id/loading_default\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"13dp\"\n        android:layout_gravity=\"center\"\n        android:textColor=\"#50ffcd\"\n        android:textSize=\"16dp\"\n        android:visibility=\"gone\"\n        android:text=\"正在加载...\"\n        android:shadowColor=\"#34000000\"\n        android:shadowRadius=\"9\"\n        android:shadowDx=\"2\"\n        android:shadowDy=\"2\" />\n\n</FrameLayout>"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <string name=\"start_plugin\">启动插件</string>\n</resources>"
  },
  {
    "path": "projects/test/none-dynamic/host/test-none-dynamic-host/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"TestHostTheme\" parent=\"@android:style/Theme.NoTitleBar.Fullscreen\" />\n\n</resources>"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId 'com.tencent.shadow.test.plugin.androidx_cases'\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.plugin.androidx_cases.app.CustomAndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId project.TEST_HOST_APP_APPLICATION_ID\n        }\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    implementation \"androidx.activity:activity:$androidx_activity_version\"\n    implementation \"androidx.appcompat:appcompat:$androidx_appcompat_version\"\n\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly 'com.tencent.shadow.core:runtime'\n}\n\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n\n    dependencies {\n        classpath 'com.tencent.shadow.core:runtime'\n        classpath 'com.tencent.shadow.core:activity-container'\n        classpath 'com.tencent.shadow.core:gradle-plugin'\n        classpath \"org.javassist:javassist:$javassist_version\"\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.test.plugin.androidx_cases\">\n\n    <application\n        android:appComponentFactory=\"com.tencent.shadow.test.plugin.androidx_cases.lib.TestComponentFactory\"\n        tools:replace=\"android:appComponentFactory\">\n\n        <activity android:name=\".lib.LiveDataWithActivityTestActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\".lib.AppComponentFactoryTestActivity\" />\n        <activity\n            android:name=\".lib.AppCompatTestActivity\"\n            android:theme=\"@style/Theme.AppCompat\" />\n    </application>\n</manifest>"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/java/com/tencent/shadow/test/plugin/androidx_cases/lib/AppCompatTestActivity.java",
    "content": "package com.tencent.shadow.test.plugin.androidx_cases.lib;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.app.AppCompatActivity;\n\npublic class AppCompatTestActivity extends AppCompatActivity {\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        LayoutInflater.Factory2 factory2 = getLayoutInflater().getFactory2();\n        String factory2Class;\n        if (factory2 == null) {\n            factory2Class = \"null\";\n        } else {\n            factory2Class = factory2.getClass().getName();\n        }\n\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n        ViewGroup item = UiUtil.makeItem(\n                this,\n                \"factory2Class\",\n                \"factory2Class\",\n                factory2Class\n        );\n\n        viewGroup.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/java/com/tencent/shadow/test/plugin/androidx_cases/lib/AppComponentFactoryTestActivity.java",
    "content": "package com.tencent.shadow.test.plugin.androidx_cases.lib;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.Nullable;\n\npublic class AppComponentFactoryTestActivity extends Activity {\n    boolean flag = false;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n        ViewGroup item = UiUtil.makeItem(\n                this,\n                \"flag\",\n                \"flag\",\n                Boolean.toString(flag)\n        );\n\n        viewGroup.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/java/com/tencent/shadow/test/plugin/androidx_cases/lib/LiveDataWithActivityTestActivity.java",
    "content": "package com.tencent.shadow.test.plugin.androidx_cases.lib;\n\n\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.TextView;\n\nimport androidx.activity.ComponentActivity;\nimport androidx.annotation.Nullable;\nimport androidx.lifecycle.MutableLiveData;\nimport androidx.lifecycle.Observer;\n\n/**\n * LiveData以Activity为LifecycleOwner\n */\npublic class LiveDataWithActivityTestActivity extends ComponentActivity {\n\n    final private MutableLiveData<String> mLiveData = new MutableLiveData<>();\n    private TextView mTextView;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        String tag = \"data\";\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n        ViewGroup item = UiUtil.makeItem(this, \"Data\", tag, \"\");\n        viewGroup.addView(item);\n        mTextView = item.findViewWithTag(tag);\n\n        final Observer<String> observer = new Observer<String>() {\n            @Override\n            public void onChanged(@Nullable final String data) {\n                mTextView.setText(data);\n            }\n        };\n\n        mLiveData.observe(this, observer);\n\n        Button button = new Button(this);\n        button.setText(\"ChangeLiveData\");\n        button.setTag(\"button\");\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                mLiveData.setValue(\"onClick\");\n            }\n        });\n        viewGroup.addView(button);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/java/com/tencent/shadow/test/plugin/androidx_cases/lib/TestComponentFactory.java",
    "content": "package com.tencent.shadow.test.plugin.androidx_cases.lib;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Build;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.core.app.CoreComponentFactory;\n\n@SuppressLint(\"RestrictedApi\")\n@RequiresApi(api = Build.VERSION_CODES.P)\npublic class TestComponentFactory extends CoreComponentFactory {\n    @NonNull\n    @Override\n    public Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {\n        Activity activity = super.instantiateActivity(cl, className, intent);\n        if (activity instanceof AppComponentFactoryTestActivity) {\n            ((AppComponentFactoryTestActivity) activity).flag = true;\n        }\n        return activity;\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/androidx-cases/test-plugin-androidx-cases/src/main/java/com/tencent/shadow/test/plugin/androidx_cases/lib/UiUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.androidx_cases.lib;\n\nimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.ScrollView;\nimport android.widget.TextView;\n\nfinal public class UiUtil {\n    public static ViewGroup setActivityContentView(Activity activity) {\n        ViewGroup viewGroup = makeItemViewGroup(activity);\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        activity.setContentView(scrollView);\n        return viewGroup;\n    }\n\n    public static ViewGroup setDialogContentView(Dialog dialog) {\n        ViewGroup viewGroup = makeItemViewGroup(dialog.getContext());\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        dialog.setContentView(scrollView);\n        return viewGroup;\n    }\n\n    public static ViewGroup setAlertDialogBuilderContentView(AlertDialog.Builder alertDialogBuilder) {\n        ViewGroup viewGroup = makeItemViewGroup(alertDialogBuilder.getContext());\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        alertDialogBuilder.setView(scrollView);\n        return viewGroup;\n    }\n\n    private static ViewGroup makeItemViewGroup(Context viewContext) {\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        return linearLayout;\n    }\n\n    private static ScrollView wrapScrollView(View view) {\n        ScrollView scrollView = new ScrollView(view.getContext());\n        scrollView.addView(view);\n        return scrollView;\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    public static ViewGroup makeItemView(Context viewContext, String labelText, String viewTag) {\n        TextView label = new TextView(viewContext);\n        label.setText(labelText + \":\");\n        label.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n\n        TextView value = new TextView(viewContext);\n        value.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n        value.setTag(viewTag);\n\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        linearLayout.setPadding(0, 10, 0, 10);\n\n        linearLayout.addView(label);\n        linearLayout.addView(value);\n        return linearLayout;\n    }\n\n    public static void setItemValue(ViewGroup viewGroupContainsItem, String viewTag, String value) {\n        TextView textView = viewGroupContainsItem.findViewWithTag(viewTag);\n        textView.setText(value);\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        setItemValue(itemView, viewTag, value);\n        return itemView;\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            AsyncGetValue asyncGetValue\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        asyncGetValue.getValue(new AsyncGetValueCallback() {\n            @Override\n            public void onGotValue(String value) {\n                setItemValue(itemView, viewTag, value);\n            }\n        });\n        return itemView;\n    }\n\n    interface AsyncGetValue {\n        void getValue(AsyncGetValueCallback callback);\n    }\n\n    interface AsyncGetValueCallback {\n        void onGotValue(String value);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId project.TEST_HOST_APP_APPLICATION_ID\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n        testInstrumentationRunner \"com.tencent.shadow.test.plugin.general_cases.app.CustomAndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId project.TEST_HOST_APP_APPLICATION_ID\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n}\n\ndependencies {\n    implementation \"com.android.support:support-annotations:$android_support_annotations_version\"\n    implementation \"com.android.support:support-v4:$android_support_version\"\n    implementation project(':custom-view')\n    pluginCompileOnly \"androidx.test.espresso:espresso-idling-resource:$espresso_version\"\n    normalImplementation \"androidx.test.espresso:espresso-idling-resource:$espresso_version\"\n    pluginCompileOnly project(':plugin-use-host-code-lib')\n\n    testImplementation \"junit:junit:$junit_version\"\n    androidTestImplementation \"androidx.test:core:$androidx_test_version\"\n    androidTestImplementation \"androidx.test.ext:junit:$androidx_test_junit_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-core:$espresso_version\"\n    androidTestImplementation \"androidx.test.espresso:espresso-remote:$espresso_version\"\n    androidTestImplementation \"androidx.test:runner:$androidx_test_version\"\n\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly 'com.tencent.shadow.core:runtime'\n}\n\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n\n    dependencies {\n        classpath 'com.tencent.shadow.core:runtime'\n        classpath 'com.tencent.shadow.core:activity-container'\n        classpath 'com.tencent.shadow.core:gradle-plugin'\n        classpath \"org.javassist:javassist:$javassist_version\"\n    }\n}\n\nshadow {\n    transform {\n//        useHostContext = ['abc']\n    }\n\n    packagePlugin {\n        pluginTypes {\n            debug {\n                loaderApkConfig = new Tuple2('test-dynamic-loader-debug.apk', ':test-dynamic-loader:assembleDebug')\n                runtimeApkConfig = new Tuple2('test-dynamic-runtime-debug.apk', ':test-dynamic-runtime:assembleDebug')\n                pluginApks {\n                    pluginApk1 {\n                        businessName = 'general-cases'\n                        partKey = 'test-plugin-general-cases'\n                        buildTask = ':test-plugin-general-cases:assemblePluginDebug'\n                        apkPath = 'projects/test/plugin/general-cases/test-plugin-general-cases/build/outputs/apk/plugin/debug/test-plugin-general-cases-plugin-debug.apk'\n                        hostWhiteList = [\"androidx.test.espresso\",//这个包添加是为了general-cases插件中可以访问测试框架的类\n                                         \"com.tencent.shadow.test.lib.plugin_use_host_code_lib.interfaces\"]//测试插件访问宿主白名单类\n                    }\n                    pluginApk4 {\n                        businessName = 'plugin-service-for-host'\n                        partKey = 'plugin-service-for-host'\n                        buildTask = ':plugin-service-for-host:assemblePluginDebug'\n                        apkPath = 'projects/test/plugin/particular-cases/plugin-service-for-host/build/outputs/apk/plugin/debug/plugin-service-for-host-plugin-debug.apk'\n                    }\n                    pluginApk5 {\n                        businessName = 'androidx-cases'\n                        partKey = 'test-plugin-androidx-cases'\n                        buildTask = ':test-plugin-androidx-cases:assemblePluginDebug'\n                        apkPath = 'projects/test/plugin/androidx-cases/test-plugin-androidx-cases/build/outputs/apk/plugin/debug/test-plugin-androidx-cases-plugin-debug.apk'\n                    }\n                }\n            }\n\n        }\n\n        loaderApkProjectPath = 'projects/test/dynamic/plugin/test-dynamic-loader'\n\n        runtimeApkProjectPath = 'projects/test/dynamic/plugin/test-dynamic-runtime'\n\n        version = 4\n        compactVersion = [1, 2, 3]\n        uuidNickName = \"1.1.5\"\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/androidTest/java/com/tencent/shadow/test/plugin/general_cases/app/ActivityLifecycleCallbacksTest.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.app;\n\nimport android.content.Intent;\nimport android.os.Build;\n\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.action.ViewActions;\nimport androidx.test.espresso.matcher.ViewMatchers;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.hamcrest.Matchers;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class ActivityLifecycleCallbacksTest extends NormalAppTest {\n\n    @Before\n    public void launchActivity() {\n        Intent intent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        intent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.ActivityLifecycleCallbacksTestActivity\"\n        );\n        ActivityScenario.launch(intent);\n    }\n\n    @Test\n    public void testRecreateRecord() {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(\"recreate\")))\n                .perform(ViewActions.click());\n\n        String api29 = \"[onActivityPreCreated, \" +\n                \"onActivityCreated, \" +\n                \"onActivityPostCreated, \" +\n                \"onActivityPreStarted, \" +\n                \"onActivityStarted, \" +\n                \"onActivityPostStarted, \" +\n                \"onActivityPreResumed, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPostResumed, \" +\n                \"onActivityPrePaused, \" +\n                \"onActivityPaused, \" +\n                \"onActivityPostPaused, \" +\n                \"onActivityPreStopped, \" +\n                \"onActivityStopped, \" +\n                \"onActivityPostStopped, \" +\n                \"onActivityPreSaveInstanceState, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityPostSaveInstanceState, \" +\n                \"onActivityPreDestroyed, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityPostDestroyed, \" +\n                \"onActivityPreCreated, \" +\n                \"onActivityCreated]\";\n\n        String api28 = \"[onActivityCreated, \" +\n                \"onActivityStarted, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPaused, \" +\n                \"onActivityStopped, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityCreated]\";\n\n        String api16 = \"[onActivityCreated, \" +\n                \"onActivityStarted, \" +\n                \"onActivityResumed, \" +\n                \"onActivityPaused, \" +\n                \"onActivitySaveInstanceState, \" +\n                \"onActivityStopped, \" +\n                \"onActivityDestroyed, \" +\n                \"onActivityCreated]\";\n\n        String expect = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? api28 : api29;\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {\n            expect = api16;\n        }\n\n        matchTextWithViewTag(\"ActivityCreateTest\", expect);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/androidTest/java/com/tencent/shadow/test/plugin/general_cases/app/CustomAndroidJUnitRunner.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.app;\n\nimport android.os.Bundle;\n\nimport androidx.test.runner.AndroidJUnitRunner;\n\npublic class CustomAndroidJUnitRunner extends AndroidJUnitRunner {\n    @Override\n    public void onCreate(Bundle arguments) {\n        //禁止Google收集数据，避免因访问不到在测试结束后等待40秒超时\n        arguments.putString(\"disableAnalytics\", Boolean.toString(true));\n        super.onCreate(arguments);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/androidTest/java/com/tencent/shadow/test/plugin/general_cases/app/NormalAppTest.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.app;\n\nimport androidx.test.espresso.Espresso;\nimport androidx.test.espresso.assertion.ViewAssertions;\nimport androidx.test.espresso.matcher.ViewMatchers;\n\nimport org.hamcrest.Matchers;\n\n/**\n * 正常安装app条件下测试general-cases-lib\n */\npublic abstract class NormalAppTest {\n    /**\n     * 检测view\n     *\n     * @param tag  view的tag\n     * @param text view上的文字\n     */\n    public void matchTextWithViewTag(String tag, String text) {\n        Espresso.onView(ViewMatchers.withTagValue(Matchers.<Object>is(tag)))\n                .check(ViewAssertions.matches(ViewMatchers.withText(text)));\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/androidTest/java/com/tencent/shadow/test/plugin/general_cases/app/PackageManagerTest.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.app;\n\nimport android.content.Intent;\n\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.core.app.ApplicationProvider;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class PackageManagerTest extends NormalAppTest {\n\n    @Before\n    public void launchActivity() {\n        Intent intent = new Intent();\n        String packageName = ApplicationProvider.getApplicationContext().getPackageName();\n        intent.setClassName(\n                packageName,\n                \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager.TestPackageManagerActivity\"\n        );\n        ActivityScenario.launch(intent);\n    }\n\n    @Test\n    public void testGetApplicationInfoClassName() {\n        matchTextWithViewTag(\"getApplicationInfo/className\", \"com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication\");\n    }\n\n    @Test\n    public void testGetApplicationInfoNativeLibraryDir() {\n        String nativeLibraryDir = ApplicationProvider.getApplicationContext().getApplicationInfo().nativeLibraryDir;\n        matchTextWithViewTag(\"getApplicationInfo/nativeLibraryDir\", nativeLibraryDir);\n    }\n\n    @Test\n    public void testGetApplicationInfoMetaData() {\n        matchTextWithViewTag(\"getApplicationInfo/metaData\", \"test_value\");\n    }\n\n    @Test\n    public void testGetActivityInfoName() {\n        matchTextWithViewTag(\"getActivityInfo/name\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager.TestPackageManagerActivity\");\n    }\n\n    @Test\n    public void testGetActivityInfoPackageName() {\n        matchTextWithViewTag(\"getActivityInfo/packageName\", \"com.tencent.shadow.test.hostapp\");\n    }\n\n    @Test\n    public void testGetPackageInfoVersionName() {\n        matchTextWithViewTag(\"getPackageInfo/versionName\", \"2.0.12\");\n    }\n\n    @Test\n    public void testGetPackageInfoVersionCode() {\n        matchTextWithViewTag(\"getPackageInfo/versionCode\", \"1\");\n    }\n\n    @Test\n    public void testQueryContentProvidersName() {\n        matchTextWithViewTag(\"queryContentProviders/size\", \">0\");\n    }\n\n    @Test\n    public void testResolveActivityByExplicitIntent() {\n        matchTextWithViewTag(\"resolveActivity/explicit\", \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager.TestPackageManagerActivity\");\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.tencent.shadow.test.plugin.general_cases\">\n\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <application\n        android:name=\".lib.gallery.TestApplication\"\n        android:icon=\"@android:drawable/sym_def_app_icon\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@android:style/Theme.NoTitleBar\">\n\n        <meta-data\n            android:name=\"test_meta\"\n            android:value=\"test_value\" />\n\n        <activity android:name=\".lib.usecases.activity.TestListActivity\" />\n\n        <activity android:name=\".lib.usecases.activity.TestActivityReCreate\" />\n\n        <activity android:name=\".lib.usecases.activity.TestActivityOnCreate\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".lib.usecases.activity.TestActivityOrientation\"\n            android:configChanges=\"orientation|screenSize|fontScale\"\n            android:windowSoftInputMode=\"adjustResize\" />\n\n        <activity\n            android:name=\".lib.usecases.activity.TestActivityWindowSoftMode\"\n            android:windowSoftInputMode=\"stateVisible\" />\n\n        <activity android:name=\".lib.usecases.activity.WindowSoftModeJumpActivity\" />\n\n        <activity android:name=\".lib.usecases.provider.TestDBContentProviderActivity\" />\n\n        <activity android:name=\".lib.usecases.activity.TestActivityReCreateBySystem\" />\n\n        <activity android:name=\".lib.usecases.service.TestStartServiceActivity\" />\n\n        <activity android:name=\".lib.usecases.receiver.TestReceiverActivity\" />\n\n        <activity android:name=\".lib.usecases.fragment.ProgrammaticallyAddFragmentActivity\" />\n\n        <activity android:name=\".lib.usecases.fragment.XmlAddFragmentActivity\" />\n\n        <activity android:name=\".lib.usecases.dialog.TestDialogActivity\" />\n\n        <activity android:name=\".lib.usecases.view.TestViewConstructorCache\" />\n\n        <activity android:name=\".lib.usecases.packagemanager.TestPackageManagerActivity\">\n            <intent-filter>\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <action android:name=\"com.tencent.test.implicit.intent\" />\n            </intent-filter>\n        </activity>\n\n        <service android:name=\".lib.usecases.service.TestService\" />\n\n        <receiver android:name=\".lib.usecases.receiver.MyReceiver\">\n            <intent-filter>\n                <action android:name=\"com.tencent.test.normal.action\" />\n            </intent-filter>\n        </receiver>\n        <receiver android:name=\".lib.usecases.receiver.ReceiverWithoutAction\" />\n\n        <activity android:name=\".lib.usecases.provider.TestFileProviderActivity\" />\n\n        <activity android:name=\".lib.usecases.activity.PrintActivityResultActivity\" />\n\n        <activity android:name=\".lib.usecases.activity.TestCallingActivity\" />\n\n        <activity android:name=\".lib.usecases.application.TestApplicationActivity\" />\n\n        <activity android:name=\".lib.usecases.interfaces.TestHostInterfaceActivity\" />\n\n        <activity android:name=\".lib.usecases.context.ActivityContextSubDirTestActivity\" />\n\n        <activity android:name=\".lib.usecases.context.ServiceContextSubDirTestActivity\" />\n        <activity\n            android:name=\".lib.usecases.context.TestThemeActivity\"\n            android:theme=\"@style/TestPluginTheme\" />\n\n        <activity android:name=\".lib.usecases.context.ApplicationContextSubDirTestActivity\" />\n        <activity android:name=\".lib.usecases.application.TestGetApplicationInfoActivity\" />\n        <activity android:name=\".lib.usecases.activity.ApplicationContextActivity\" />\n        <activity android:name=\".lib.usecases.fragment.TargetFragmentTestActivity\" />\n        <activity android:name=\".lib.usecases.fragment.FragmentStartedActivity\" />\n        <activity android:name=\".lib.usecases.context.RegisterNullReceiverActivity\" />\n        <activity android:name=\".lib.usecases.dialog.TestAlertDialogActivity\" />\n        <activity android:name=\".lib.usecases.instrumentation.TestInstrumentationActivity\" />\n        <activity android:name=\".lib.usecases.application.ActivityLifecycleCallbacksTestActivity\" />\n        <activity android:name=\".lib.usecases.classloader.TestBootClassloaderActivity\" />\n        <activity android:name=\".lib.usecases.context.TestLayoutInflaterActivity\" />\n        <activity android:name=\".lib.usecases.context.ContextGetPackageCodePathTestActivity\" />\n        <activity android:name=\".lib.usecases.view.TestViewIdActivity\" />\n        <activity android:name=\".lib.usecases.view.TestNullRefInXmlActivity\" />\n\n        <provider\n            android:name=\".lib.usecases.provider.TestProvider\"\n            android:grantUriPermissions=\"true\"\n            android:authorities=\"${applicationId}.provider.test\" />\n\n        <provider\n            android:name=\"android.support.v4.content.FileProvider\"\n            android:authorities=\"${applicationId}.general_cases.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/filepaths\" />\n        </provider>\n    </application>\n\n</manifest>"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/gallery/TestApplication.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.gallery;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\npublic class TestApplication extends Application {\n\n    private static TestApplication sInstence;\n\n    public boolean isOnCreate;\n\n    final private TestActivityLifecycleCallbacks alc = new TestActivityLifecycleCallbacks(\n            \"com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.ActivityLifecycleCallbacksTestActivity\");\n\n    @Override\n    public void onCreate() {\n        sInstence = this;\n        isOnCreate = true;\n        super.onCreate();\n\n        registerActivityLifecycleCallbacks(alc);\n\n        //额外添加一个callback，构造通知遍历多个callback的场景\n        registerActivityLifecycleCallbacks(new TestActivityLifecycleCallbacks(\"TestForRegisterInPreCreatedCallback\"));\n    }\n\n    public static TestApplication getInstance() {\n        return sInstence;\n    }\n\n    public List<String> getTestActivityLifecycleCallbacksRecord() {\n        return alc.recordList;\n    }\n}\n\nclass TestActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {\n    final private String targetActivityName;\n    final List<String> recordList = new LinkedList<>();\n\n    TestActivityLifecycleCallbacks(String targetActivityName) {\n        this.targetActivityName = targetActivityName;\n    }\n\n    private boolean isTargetActivity(Activity activity) {\n        return activity.getClass().getName().equals(targetActivityName);\n    }\n\n    @Override\n    public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreCreated\");\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            TestActivityLifecycleCallbacks testForRegisterInPreCreatedCallback = new TestActivityLifecycleCallbacks(\"TestForRegisterInPreCreatedCallback\");\n            TestApplication.getInstance().registerActivityLifecycleCallbacks(\n                    testForRegisterInPreCreatedCallback\n            );\n            TestApplication.getInstance().unregisterActivityLifecycleCallbacks(\n                    testForRegisterInPreCreatedCallback\n            );\n        }\n    }\n\n    @Override\n    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityCreated\");\n        }\n    }\n\n    @Override\n    public void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostCreated\");\n        }\n    }\n\n    @Override\n    public void onActivityPreStarted(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreStarted\");\n        }\n    }\n\n    @Override\n    public void onActivityStarted(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityStarted\");\n        }\n    }\n\n    @Override\n    public void onActivityPostStarted(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostStarted\");\n        }\n    }\n\n    @Override\n    public void onActivityPreResumed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreResumed\");\n        }\n    }\n\n    @Override\n    public void onActivityResumed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityResumed\");\n        }\n    }\n\n    @Override\n    public void onActivityPostResumed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostResumed\");\n        }\n    }\n\n    @Override\n    public void onActivityPrePaused(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPrePaused\");\n        }\n    }\n\n    @Override\n    public void onActivityPaused(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPaused\");\n        }\n    }\n\n    @Override\n    public void onActivityPostPaused(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostPaused\");\n        }\n    }\n\n    @Override\n    public void onActivityPreStopped(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreStopped\");\n        }\n    }\n\n    @Override\n    public void onActivityStopped(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityStopped\");\n        }\n    }\n\n    @Override\n    public void onActivityPostStopped(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostStopped\");\n        }\n    }\n\n    @Override\n    public void onActivityPreSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreSaveInstanceState\");\n        }\n    }\n\n    @Override\n    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivitySaveInstanceState\");\n        }\n    }\n\n    @Override\n    public void onActivityPostSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostSaveInstanceState\");\n        }\n    }\n\n    @Override\n    public void onActivityPreDestroyed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPreDestroyed\");\n        }\n    }\n\n    @Override\n    public void onActivityDestroyed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityDestroyed\");\n        }\n    }\n\n    @Override\n    public void onActivityPostDestroyed(@NonNull Activity activity) {\n        if (isTargetActivity(activity)) {\n            recordList.add(\"onActivityPostDestroyed\");\n        }\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/gallery/util/ToastUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.gallery.util;\n\nimport android.content.Context;\nimport android.widget.Toast;\n\npublic class ToastUtil {\n\n    public static void showToast(Context context, String message) {\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/gallery/util/UiUtil.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.gallery.util;\n\nimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\nimport android.widget.ScrollView;\nimport android.widget.TextView;\n\nfinal public class UiUtil {\n    public static ViewGroup setActivityContentView(Activity activity) {\n        ViewGroup viewGroup = makeItemViewGroup(activity);\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        activity.setContentView(scrollView);\n        return viewGroup;\n    }\n\n    public static ViewGroup setDialogContentView(Dialog dialog) {\n        ViewGroup viewGroup = makeItemViewGroup(dialog.getContext());\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        dialog.setContentView(scrollView);\n        return viewGroup;\n    }\n\n    public static ViewGroup setAlertDialogBuilderContentView(AlertDialog.Builder alertDialogBuilder) {\n        ViewGroup viewGroup = makeItemViewGroup(alertDialogBuilder.getContext());\n        ScrollView scrollView = wrapScrollView(viewGroup);\n        alertDialogBuilder.setView(scrollView);\n        return viewGroup;\n    }\n\n    private static ViewGroup makeItemViewGroup(Context viewContext) {\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        return linearLayout;\n    }\n\n    private static ScrollView wrapScrollView(View view) {\n        ScrollView scrollView = new ScrollView(view.getContext());\n        scrollView.addView(view);\n        return scrollView;\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    public static ViewGroup makeItemView(Context viewContext, String labelText, String viewTag) {\n        TextView label = new TextView(viewContext);\n        label.setText(labelText + \":\");\n        label.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n\n        TextView value = new TextView(viewContext);\n        value.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));\n        value.setTag(viewTag);\n\n        LinearLayout linearLayout = new LinearLayout(viewContext);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        linearLayout.setPadding(0, 10, 0, 10);\n\n        linearLayout.addView(label);\n        linearLayout.addView(value);\n        return linearLayout;\n    }\n\n    public static void setItemValue(ViewGroup viewGroupContainsItem, String viewTag, String value) {\n        TextView textView = viewGroupContainsItem.findViewWithTag(viewTag);\n        textView.setText(value);\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        setItemValue(itemView, viewTag, value);\n        return itemView;\n    }\n\n    public static ViewGroup makeItem(\n            Context viewContext,\n            String labelText,\n            final String viewTag,\n            AsyncGetValue asyncGetValue\n    ) {\n        final ViewGroup itemView = makeItemView(viewContext, labelText, viewTag);\n        asyncGetValue.getValue(new AsyncGetValueCallback() {\n            @Override\n            public void onGotValue(String value) {\n                setItemValue(itemView, viewTag, value);\n            }\n        });\n        return itemView;\n    }\n\n    interface AsyncGetValue {\n        void getValue(AsyncGetValueCallback callback);\n    }\n\n    interface AsyncGetValueCallback {\n        void onGotValue(String value);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/SimpleIdlingResource.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases;\n\nimport androidx.test.espresso.IdlingResource;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A very simple implementation of {@link IdlingResource}.\n * <p>\n * Consider using CountingIdlingResource from espresso-contrib package if you use this class from\n * multiple threads or need to keep a count of pending operations.\n */\n\npublic class SimpleIdlingResource implements IdlingResource {\n\n    private volatile ResourceCallback mCallback;\n\n    // Idleness is controlled with this boolean.\n    private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);\n\n    @Override\n    public String getName() {\n        return this.getClass().getName();\n    }\n\n    @Override\n    public boolean isIdleNow() {\n        return mIsIdleNow.get();\n    }\n\n    @Override\n    public void registerIdleTransitionCallback(ResourceCallback callback) {\n        mCallback = callback;\n    }\n\n    /**\n     * Sets the new idle state, if isIdleNow is true, it pings the {@link ResourceCallback}.\n     *\n     * @param isIdleNow false if there are pending operations, true if idle.\n     */\n    public void setIdleState(boolean isIdleNow) {\n        mIsIdleNow.set(isIdleNow);\n        if (isIdleNow && mCallback != null) {\n            mCallback.onTransitionToIdle();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/WithIdlingResourceActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport androidx.test.espresso.IdlingRegistry;\n\npublic class WithIdlingResourceActivity extends Activity {\n\n    protected SimpleIdlingResource mIdlingResource = new SimpleIdlingResource();\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(mIdlingResource);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        IdlingRegistry.getInstance().unregister(mIdlingResource);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/ApplicationContextActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.widget.TextView;\n\npublic class ApplicationContextActivity extends Activity {\n\n    private boolean noError = false;\n\n    @Override\n    protected void attachBaseContext(Context newBase) {\n        super.attachBaseContext(newBase);\n        Context applicationContext = getApplicationContext();\n        noError = applicationContext != null;\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        TextView textView = new TextView(this);\n        textView.setText(Boolean.toString(noError));\n        textView.setTag(\"noError\");\n        setContentView(textView);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/PrintActivityResultActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.WithIdlingResourceActivity;\n\npublic class PrintActivityResultActivity extends WithIdlingResourceActivity {\n\n    private TextView mText;\n\n    public static String KEY_FROM_JUMP = \"fromJump\";\n    public static String KEY_TARGET_CLASS = \"targetClassName\";\n    public static String KEY_WAIT_FOR_RESULT = \"waitForResult\";\n\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_common);\n        mText = findViewById(R.id.text);\n    }\n\n    public void doClick(View view) {\n        boolean wait = getIntent().getBooleanExtra(KEY_WAIT_FOR_RESULT, true);\n        if (wait) {\n            mIdlingResource.setIdleState(false);\n        }\n        String className = getIntent().getStringExtra(KEY_TARGET_CLASS);\n        Intent intent = new Intent();\n        intent.setClassName(this, className);\n        intent.putExtra(KEY_FROM_JUMP, true);\n        startActivityForResult(intent, 1001);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        mIdlingResource.setIdleState(true);\n        String txt = data.getStringExtra(\"result\");\n        mText.setText(txt);\n\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestActivityOnCreate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\n\npublic class TestActivityOnCreate extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_activity_lifecycle);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        ToastUtil.showToast(this, \"onStart\");\n    }\n\n    @Override\n    protected void onRestart() {\n        super.onRestart();\n        ToastUtil.showToast(this, \"onRestart\");\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        ToastUtil.showToast(this, \"onResume\");\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        ToastUtil.showToast(this, \"onSaveInstanceState\");\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        ToastUtil.showToast(this, \"onRestoreInstanceState\");\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        ToastUtil.showToast(this, \"onStop\");\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        ToastUtil.showToast(this, \"onDestroy\");\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestActivityOrientation.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.content.pm.ActivityInfo;\nimport android.content.res.Configuration;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\n\n\npublic class TestActivityOrientation extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_orientation);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n\n    public void setOrientation(View view) {\n        int orientation = getRequestedOrientation();\n        if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {\n            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n        } else {\n            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        }\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        super.onConfigurationChanged(newConfig);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestActivityReCreate.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\n\npublic class TestActivityReCreate extends Activity {\n\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_recreate);\n        TextView textView = findViewById(R.id.tv_msg);\n        String reCreateMsg = getIntent().getStringExtra(\"reCreateMsg\");\n        textView.setText(\"reCreateMsg:\" + reCreateMsg);\n        ToastUtil.showToast(this, \"onCreate\");\n    }\n\n    public void reCreate(View view) {\n        getIntent().putExtra(\"reCreateMsg\", \"afterReCreate\");\n        recreate();\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        ToastUtil.showToast(this, \"onStart\");\n    }\n\n    @Override\n    protected void onRestart() {\n        super.onRestart();\n        ToastUtil.showToast(this, \"onRestart\");\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        ToastUtil.showToast(this, \"onResume\");\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        ToastUtil.showToast(this, \"onSaveInstanceState\");\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n        ToastUtil.showToast(this, \"onRestoreInstanceState\");\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        ToastUtil.showToast(this, \"onStop\");\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        ToastUtil.showToast(this, \"onDestroy\");\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestActivityReCreateBySystem.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\npublic class TestActivityReCreateBySystem extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_test_re_create_by_system);\n        String url = \"url : \" + getIntent().getStringExtra(\"url\");\n        ((TextView) findViewById(R.id.url_tv)).setText(url);\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestActivityWindowSoftMode.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.widget.EditText;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\n\npublic class TestActivityWindowSoftMode extends Activity {\n\n    private EditText mEditText;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        WindowManager.LayoutParams layoutParams = getWindow().getAttributes();\n        boolean is_state_visible = layoutParams.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;\n\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"测试WindowSoftMode\",\n                        \"TAG_SOFT_MODE\",\n                        \"SOFT_INPUT_STATE_VISIBLE:\" + is_state_visible\n                )\n        );\n\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestCallingActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestCallingActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getCallingActivity()\",\n                        \"TAG_GET_CALLING_ACTIVITY\",\n                        getCallingActivity() != null ? getCallingActivity().toShortString() : \"null\"\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/TestListActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.BaseAdapter;\nimport android.widget.ListView;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class TestListActivity extends Activity {\n    List<String> mItemList = new ArrayList<>();\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_list);\n\n        ArrayList<String> activities = getIntent().getStringArrayListExtra(\"activities\");\n        if (activities != null) {\n            mItemList.addAll(activities);\n        }\n\n        ListView listView = findViewById(R.id.al_list);\n        listView.setAdapter(new InnerAdapter());\n        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                String className = mItemList.get(position);\n                try {\n                    Intent intent = new Intent(TestListActivity.this, Class.forName(className));\n                    startActivity(intent);\n                } catch (ClassNotFoundException e) {\n                    e.printStackTrace();\n                }\n            }\n        });\n    }\n\n    class InnerAdapter extends BaseAdapter {\n        @Override\n        public int getCount() {\n            return mItemList.size();\n        }\n\n        @Override\n        public Object getItem(int position) {\n            return mItemList.get(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            Holder holder;\n            if (convertView == null) {\n                convertView = View.inflate(getApplicationContext(), R.layout.layout_list_item, null);\n                holder = new Holder();\n                holder.textView = convertView.findViewById(R.id.lli_text);\n                convertView.setTag(holder);\n            } else {\n                holder = (Holder) convertView.getTag();\n            }\n            String className = getItem(position).toString();\n            if (className.indexOf(\".\") != -1) {\n                className = className.substring(className.lastIndexOf(\".\") + 1);\n            }\n            holder.textView.setText(className);\n            return convertView;\n        }\n\n        class Holder {\n            TextView textView;\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/activity/WindowSoftModeJumpActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.activity;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.WithIdlingResourceActivity;\n\npublic class WindowSoftModeJumpActivity extends WithIdlingResourceActivity {\n\n    private TextView mText;\n\n    public static String KEY_FROM_JUMP = \"fromJump\";\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_common);\n        mText = findViewById(R.id.text);\n    }\n\n    public void doClick(View view) {\n        mIdlingResource.setIdleState(false);\n        Intent intent = new Intent(this, TestActivityWindowSoftMode.class);\n        intent.putExtra(KEY_FROM_JUMP, true);\n        startActivityForResult(intent, 1001);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        mIdlingResource.setIdleState(true);\n        String txt = data.getStringExtra(\"result\");\n        mText.setText(txt);\n\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/application/ActivityLifecycleCallbacksTestActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.application;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\nimport java.util.List;\n\n/**\n * 在\n * com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication\n * 中注册一个ActivityLifecycleCallbacks，专门监听\n * com.tencent.shadow.test.plugin.general_cases.lib.usecases.application.TestApplicationActivity\n * 的生命周期。\n * 然后用这个Activity打印出监听记录进行测试。\n */\npublic class ActivityLifecycleCallbacksTestActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        List<String> record = TestApplication.getInstance().getTestActivityLifecycleCallbacksRecord();\n\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"ActivityCreateTest\",\n                        \"ActivityCreateTest\",\n                        record.toString()\n                )\n        );\n\n        Button recreateButton = new Button(this);\n        recreateButton.setText(\"recreate\");\n        recreateButton.setTag(\"recreate\");\n        recreateButton.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                ActivityLifecycleCallbacksTestActivity.this.recreate();\n            }\n        });\n        viewGroup.addView(recreateButton);\n    }\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/application/TestApplicationActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.application;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.TestApplication;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestApplicationActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"TestApplication被调用过onCreate\",\n                        \"TAG_IS_ON_CREATE\",\n                        Boolean.toString(TestApplication.getInstance().isOnCreate)\n                )\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            mItemViewGroup.addView(\n                    UiUtil.makeItem(\n                            this,\n                            \"Application.getProcessName()\",\n                            \"Application.getProcessName()\",\n                            Application.getProcessName()\n                    )\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/application/TestGetApplicationInfoActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.application;\n\nimport static android.content.pm.PackageManager.GET_META_DATA;\n\nimport android.app.Activity;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestGetApplicationInfoActivity extends Activity {\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        parseApplicationInfo(\"Context\", this.getApplicationInfo());\n        try {\n            parseApplicationInfo(\"PackageManagerGetSelf\",\n                    getPackageManager().getApplicationInfo(getPackageName(), GET_META_DATA));\n            parseApplicationInfo(\"PackageManagerGetOtherInstalled\",\n                    getPackageManager().getApplicationInfo(\"android\", GET_META_DATA));\n        } catch (PackageManager.NameNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private void parseApplicationInfo(String tag, ApplicationInfo info) {\n        String sourceDir = info.sourceDir;\n        String nativeLibraryDir = info.nativeLibraryDir;\n        Bundle metaData = info.metaData;\n        String className = info.className;\n\n        makeItem(tag + \":sourceDir\", \"TAG_sourceDir_\" + tag,\n                sourceDir\n        );\n        makeItem(tag + \":nativeLibraryDir\", \"TAG_nativeLibraryDir_\" + tag,\n                nativeLibraryDir\n        );\n        makeItem(tag + \":metaData\", \"TAG_metaData_\" + tag,\n                metaData != null ? metaData.getString(\"test_meta\") : null\n        );\n        makeItem(tag + \":className\", \"TAG_className_\" + tag,\n                className\n        );\n    }\n\n    private void makeItem(\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        ViewGroup item = UiUtil.makeItem(this, labelText, viewTag, value);\n        mItemViewGroup.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/classloader/TestBootClassloaderActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.classloader;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\n/**\n * 插件中自己声明了一个和系统类重名的类org.xmlpull.v1.XmlPullParser\n * 测试在插件环境下加载它是从哪个ClassLoader加载的\n */\npublic class TestBootClassloaderActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        String xmlPullParserFrom;\n        try {\n            xmlPullParserFrom = getClassLoader().loadClass(\"org.xmlpull.v1.XmlPullParser\")\n                    .getClassLoader().getClass().getName();\n        } catch (ClassNotFoundException e) {\n            xmlPullParserFrom = \"ClassNotFoundException\";\n        }\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"xmlPullParserFrom\",\n                        \"xmlPullParserFrom\",\n                        xmlPullParserFrom\n                )\n        );\n    }\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/ActivityContextSubDirTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\npublic class ActivityContextSubDirTestActivity extends SubDirContextThemeWrapperTestActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        fillTestValues(this);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/ApplicationContextSubDirTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\npublic class ApplicationContextSubDirTestActivity extends SubDirContextThemeWrapperTestActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        fillTestValues(getApplication());\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/ContextGetPackageCodePathTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class ContextGetPackageCodePathTestActivity extends Activity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"PackageCodePath\",\n                        \"PackageCodePath\",\n                        getPackageCodePath()\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/RegisterNullReceiverActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Bundle;\nimport android.os.Parcel;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\nimport java.util.Arrays;\n\npublic class RegisterNullReceiverActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        String string;\n        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);\n        Intent intent = registerReceiver(null, intentFilter);\n\n        if (intent != null) {\n            Parcel parcel = Parcel.obtain();\n            intent.writeToParcel(parcel, 0);\n            byte[] byteArray = parcel.marshall();\n            string = Arrays.toString(byteArray);\n            parcel.recycle();\n        } else {\n            string = \"intent == null\";\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"byteArray\",\n                        \"byteArray\",\n                        string\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/ServiceContextSubDirTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.support.annotation.Nullable;\n\nimport androidx.test.espresso.IdlingRegistry;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.SimpleIdlingResource;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.service.TestService;\n\npublic class ServiceContextSubDirTestActivity extends SubDirContextThemeWrapperTestActivity {\n\n    final private SimpleIdlingResource mIdlingResource = new SimpleIdlingResource();\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mIdlingResource.setIdleState(false);\n        IdlingRegistry.getInstance().register(mIdlingResource);\n\n        Intent intent = new Intent(this, TestService.class);\n        bindService(intent, new ServiceConnection() {\n            @Override\n            public void onServiceConnected(ComponentName name, IBinder service) {\n                TestService.MyLocalServiceBinder binder = (TestService.MyLocalServiceBinder) service;\n                TestService testService = binder.getMyLocalService();\n                fillTestValues(testService);\n                mIdlingResource.setIdleState(true);\n            }\n\n            @Override\n            public void onServiceDisconnected(ComponentName name) {\n\n            }\n        }, BIND_AUTO_CREATE);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        IdlingRegistry.getInstance().unregister(mIdlingResource);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/SubDirContextThemeWrapperTestActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport static android.os.Environment.DIRECTORY_MUSIC;\nimport static android.os.Environment.DIRECTORY_PODCASTS;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.RequiresApi;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.FilenameFilter;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\n\nabstract class SubDirContextThemeWrapperTestActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n    }\n\n\n    protected void fillTestValues(Context testContext) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            makeItem(\"getDataDir()\", \"TAG_GET_DATA_DIR\",\n                    testContext.getDataDir().getAbsolutePath()\n            );\n        }\n\n        makeItem(\"getFilesDir()\", \"TAG_GET_FILES_DIR\",\n                testContext.getFilesDir().getAbsolutePath()\n        );\n\n        makeItem(\"openFileInput(\\\"foo\\\")\", \"TAG_OPEN_FILE_INPUT_FOO\",\n                getOpenFileInputAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"openFileInput(\\\"bar\\\")\", \"TAG_OPEN_FILE_INPUT_BAR\",\n                getOpenFileInputAbsolutePath(testContext, \"bar\")\n        );\n\n        makeItem(\"openFileOutput(\\\"foo\\\", MODE_PRIVATE)\", \"TAG_OPEN_FILE_OUTPUT_FOO\",\n                getOpenFileOutputAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"openFileOutput(\\\"bar\\\", MODE_PRIVATE)\", \"TAG_OPEN_FILE_OUTPUT_BAR\",\n                getOpenFileOutputAbsolutePath(testContext, \"bar\")\n        );\n\n        makeItem(\"deleteFile(\\\"foo\\\")\", \"TAG_DELETE_FILE_FOO\",\n                isDeleteFileSuccess(testContext, \"foo\")\n        );\n\n        makeItem(\"deleteFile(\\\"bar\\\")\", \"TAG_DELETE_FILE_BAR\",\n                isDeleteFileSuccess(testContext, \"bar\")\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getNoBackupFilesDir()\", \"TAG_GET_NBF_DIR\",\n                    testContext.getNoBackupFilesDir().getAbsolutePath()\n            );\n        }\n\n        File externalMusicDir = testContext.getExternalFilesDir(DIRECTORY_MUSIC);\n        makeItem(\"getExternalFilesDir(DIRECTORY_MUSIC)\", \"TAG_GET_EFD_MUSIC\",\n                externalMusicDir == null ? \"null\" : externalMusicDir.getAbsolutePath()\n        );\n\n        File externalPodcastsDir = testContext.getExternalFilesDir(DIRECTORY_PODCASTS);\n        makeItem(\"getExternalFilesDir(DIRECTORY_MUSIC)\", \"TAG_GET_EFD_PODCASTS\",\n                externalPodcastsDir == null ? \"null\" : externalPodcastsDir.getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            File[] externalMusicDirs = testContext.getExternalFilesDirs(DIRECTORY_MUSIC);\n            makeItem(\"getExternalFilesDirs(DIRECTORY_MUSIC)\", \"TAG_GET_EFDS_MUSIC\",\n                    Arrays.toString(externalMusicDirs)\n            );\n\n            File[] externalPodcastsDirs = testContext.getExternalFilesDirs(DIRECTORY_PODCASTS);\n            makeItem(\"getExternalFilesDirs(DIRECTORY_MUSIC)\", \"TAG_GET_EFDS_PODCASTS\",\n                    Arrays.toString(externalPodcastsDirs)\n            );\n        }\n\n        makeItem(\"getObbDir()\", \"TAG_GET_OBB_DIR\",\n                testContext.getObbDir().getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            makeItem(\"getObbDirs()\", \"TAG_GET_OBB_DIRS\",\n                    Arrays.toString(testContext.getObbDirs())\n            );\n        }\n\n        makeItem(\"getCacheDir()\", \"TAG_GET_CACHE_DIR\",\n                testContext.getCacheDir().getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getCodeCacheDir()\", \"TAG_GET_CODE_CACHE_DIR\",\n                    testContext.getCodeCacheDir().getAbsolutePath()\n            );\n        }\n\n        File externalCacheDir = testContext.getExternalCacheDir();\n        makeItem(\"getExternalCacheDir()\", \"TAG_GET_EXT_CACHE_DIR\",\n                externalCacheDir == null ? \"null\" : externalCacheDir.getAbsolutePath()\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            makeItem(\"getExternalCacheDirs()\", \"TAG_GET_EXT_CACHE_DIRS\",\n                    Arrays.toString(testContext.getExternalCacheDirs())\n            );\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            makeItem(\"getExternalMediaDirs()\", \"TAG_GET_EXT_MEDIA_DIRS\",\n                    Arrays.toString(testContext.getExternalMediaDirs())\n            );\n        }\n\n        makeItem(\"getDir(\\\"foo\\\",MODE_PRIVATE)\", \"TAG_GET_DIR_FOO\",\n                testContext.getDir(\"foo\", MODE_PRIVATE).getAbsolutePath()\n        );\n\n        makeItem(\"getDir(\\\"bar\\\",MODE_PRIVATE)\", \"TAG_GET_DIR_BAR\",\n                testContext.getDir(\"bar\", MODE_PRIVATE).getAbsolutePath()\n        );\n\n        makeItem(\"getSharedPreferences(\\\"foo\\\",MODE_PRIVATE)\", \"TAG_GET_SP_FOO\",\n                getSharedPreferencesAbsolutePath(testContext, \"foo\")\n        );\n\n        makeItem(\"getSharedPreferences(\\\"bar\\\",MODE_PRIVATE)\", \"TAG_GET_SP_BAR\",\n                getSharedPreferencesAbsolutePath(testContext, \"bar\")\n        );\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            makeItem(\"deleteSharedPreferences(\\\"foo\\\")\", \"TAG_DEL_SP_FOO\",\n                    isDeleteSharedPreferencesSuccess(testContext, \"foo\")\n            );\n\n            makeItem(\"deleteSharedPreferences(\\\"bar\\\")\", \"TAG_DEL_SP_BAR\",\n                    isDeleteSharedPreferencesSuccess(testContext, \"bar\")\n            );\n        }\n\n        makeItem(\"openOrCreateDatabase(\\\"foo\\\",MODE_PRIVATE,null)\", \"TAG_OOCD3_FOO\",\n                testContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null).getPath()\n        );\n        testContext.deleteDatabase(\"foo\");\n\n        makeItem(\"openOrCreateDatabase(\\\"bar\\\",MODE_PRIVATE,null)\", \"TAG_OOCD3_BAR\",\n                testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null).getPath()\n        );\n        testContext.deleteDatabase(\"bar\");\n\n        makeItem(\"openOrCreateDatabase(\\\"foo\\\",MODE_PRIVATE,null,null)\", \"TAG_OOCD4_FOO\",\n                testContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null, null).getPath()\n        );\n        testContext.deleteDatabase(\"foo\");\n\n        makeItem(\"openOrCreateDatabase(\\\"bar\\\",MODE_PRIVATE,null,null)\", \"TAG_OOCD4_BAR\",\n                testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null, null).getPath()\n        );\n        testContext.deleteDatabase(\"bar\");\n\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {\n            String value = \"\";\n            try {\n                testContext.moveDatabaseFrom(this, \"foo\");\n            } catch (Exception e) {\n                value = e.getMessage();\n            }\n            makeItem(\"moveDatabaseFrom(this,\\\"foo\\\")\", \"TAG_MOVE_DB_FROM_FOO\",\n                    value\n            );\n\n            try {\n                testContext.moveDatabaseFrom(this, \"bar\");\n            } catch (Exception e) {\n                value = e.getMessage();\n            }\n            makeItem(\"moveDatabaseFrom(this,\\\"bar\\\")\", \"TAG_MOVE_DB_FROM_BAR\",\n                    value\n            );\n        }\n\n        makeItem(\"deleteDatabase(\\\"foo_d\\\")\", \"TAG_DELETE_DB_FOO\",\n                isDeleteDatabaseSuccess(testContext, \"foo_d\")\n        );\n\n        makeItem(\"deleteDatabase(\\\"bar_d\\\")\", \"TAG_DELETE_DB_BAR\",\n                isDeleteDatabaseSuccess(testContext, \"bar_d\")\n        );\n\n        makeItem(\"getDatabasePath(\\\"foo\\\")\", \"TAG_GET_DATABASE_PATH_FOO\",\n                testContext.getDatabasePath(\"foo\").getAbsolutePath()\n        );\n\n        makeItem(\"getDatabasePath(\\\"bar\\\")\", \"TAG_GET_DATABASE_PATH_BAR\",\n                testContext.getDatabasePath(\"bar\").getAbsolutePath()\n        );\n\n        // 传绝对路径给getDatabasePath应该返回路径不变的文件\n        makeItem(\"getDatabasePath(\\\"/foo/bar\\\")\", \"TAG_GET_DATABASE_ABSOLUTE_PATH_FOO_BAR\",\n                testContext.getDatabasePath(\"/foo/bar\").getAbsolutePath()\n        );\n\n\n        Context hostContext = getApplication().getBaseContext();\n        hostContext.openOrCreateDatabase(\"foo\", MODE_PRIVATE, null);\n        testContext.openOrCreateDatabase(\"bar\", MODE_PRIVATE, null);\n        String[] databaseListArray = testContext.databaseList();\n        List<String> databaseList = new LinkedList<>();\n        Collections.addAll(databaseList, databaseListArray);\n        Iterator<String> iterator = databaseList.iterator();\n        String s;\n        while (iterator.hasNext()) {\n            s = iterator.next();\n            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {\n                if (s.endsWith(\"-wal\") || s.endsWith(\"-shm\")) {\n                    iterator.remove();\n                }\n            } else {\n                if (s.endsWith(\"-journal\")) {\n                    iterator.remove();\n                }\n            }\n        }\n\n        makeItem(\"databaseList()\", \"TAG_DATABASE_LIST\",\n                Arrays.toString(databaseList.toArray())\n        );\n        hostContext.deleteDatabase(\"foo\");\n        testContext.deleteDatabase(\"bar\");\n    }\n\n    private String getOpenFileInputAbsolutePath(Context context, String name) {\n        String result = \"\";\n        try {\n            context.openFileInput(name);\n        } catch (FileNotFoundException e) {\n            String message = e.getMessage();\n            int i = message.indexOf(name);\n            result = message.substring(0, i + name.length());\n        }\n        return result;\n    }\n\n    private String getOpenFileOutputAbsolutePath(Context context, String name) {\n        File file = new File(context.getFilesDir(), name);\n        if (file.exists()) {\n            throw new RuntimeException(\"测试文件不能提前存在\");\n        }\n        try {\n            FileOutputStream fileOutputStream = context.openFileOutput(name, MODE_PRIVATE);\n            fileOutputStream.write(1);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        if (!file.delete()) {\n            throw new RuntimeException(\"测试文件应该被创建出来了\");\n        }\n        return file.getAbsolutePath();\n    }\n\n    private String isDeleteFileSuccess(Context context, String name) {\n        File foo = new File(context.getFilesDir(), name);\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteFile(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private String getSharedPreferencesAbsolutePath(Context context, final String name) {\n        SharedPreferences sharedPreferences\n                = context.getSharedPreferences(name, MODE_PRIVATE);\n        boolean commit = sharedPreferences.edit().putString(\"test\", \"test\").commit();\n        if (!commit) {\n            throw new RuntimeException(\"commit failed\");\n        }\n\n        Context hostContext = getApplication().getBaseContext();\n        File dataDir = hostContext.getFilesDir().getParentFile();\n        File sharedPrefsDir = new File(dataDir, \"shared_prefs\");\n\n        File[] files = sharedPrefsDir.listFiles(new FilenameFilter() {\n            @Override\n            public boolean accept(File dir, String fileName) {\n                return fileName.contains(name);\n            }\n        });\n        if (files.length != 1) {\n            throw new RuntimeException(\"匹配文件数量不对。\");\n        }\n        String result = files[0].getAbsolutePath();\n\n        if (!files[0].delete()) {\n            throw new RuntimeException(\"删除测试文件失败\");\n        }\n\n        return result;\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.N)\n    private String isDeleteSharedPreferencesSuccess(Context context, String name) {\n        File foo = new File(getSharedPreferencesAbsolutePath(context, name));\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteSharedPreferences(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private String isDeleteDatabaseSuccess(Context context, String name) {\n        File foo = context.getDatabasePath(name);\n        try {\n            boolean newFile = foo.createNewFile();\n            if (!newFile) {\n                throw new RuntimeException(\"没能创建新文件\");\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        if (context.deleteDatabase(name)) {\n            return \"success\";\n        } else {\n            return \"fail\";\n        }\n    }\n\n    private void makeItem(\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        ViewGroup item = UiUtil.makeItem(this, labelText, viewTag, value);\n        mItemViewGroup.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/TestLayoutInflaterActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestLayoutInflaterActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"FactoryClassNameBeforeSet\",\n                        \"FactoryClassNameBeforeSet\",\n                        getFactoryClassName(getLayoutInflater())\n                )\n        );\n\n        LayoutInflater layoutInflater = getLayoutInflater();\n        layoutInflater.setFactory2(new TestFactory2());\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"FactoryClassNameAfterSet\",\n                        \"FactoryClassNameAfterSet\",\n                        getFactoryClassName(layoutInflater)\n                )\n        );\n    }\n\n    private static String getFactoryClassName(LayoutInflater layoutInflater) {\n        LayoutInflater.Factory factory = layoutInflater.getFactory();\n        if (factory == null) {\n            return \"null\";\n        } else {\n            return factory.getClass().getName();\n        }\n    }\n}\n\nclass TestFactory2 implements LayoutInflater.Factory2 {\n\n    @Nullable\n    @Override\n    public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {\n        return null;\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/context/TestThemeActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.context;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ContextThemeWrapper;\nimport android.view.ViewGroup;\nimport android.view.Window;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\nimport java.lang.reflect.Method;\n\npublic class TestThemeActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        boolean hasFeatureActionBar = getWindow().hasFeature(Window.FEATURE_ACTION_BAR);\n        if (!hasFeatureActionBar) {\n            throw new IllegalStateException(\"没有FEATURE_ACTION_BAR feature无法测试\" +\n                    \"Activity.initWindowDecorActionBar中加载宿主icon资源\");\n        }\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"ApplicationThemeName\",\n                        \"ApplicationThemeName\",\n                        getThemeName(getApplicationContext())\n                )\n        );\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"ActivityThemeName\",\n                        \"ActivityThemeName\",\n                        getThemeName(this)\n                )\n        );\n    }\n\n    private static String getThemeName(Context context) {\n        try {\n            Class<?> clazz = ContextThemeWrapper.class;\n            Method method = clazz.getMethod(\"getThemeResId\");\n            method.setAccessible(true);\n            int themeResId = (Integer) method.invoke(context);\n            return context.getResources().getResourceName(themeResId);\n        } catch (Exception e) {\n            return \"Exception: \" + e.getMessage();\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/dialog/TestAlertDialogActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.dialog;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestAlertDialogActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        AlertDialog.Builder builder = new AlertDialog.Builder(new ContextWrapper(this) {\n            @Override\n            public Context getApplicationContext() {\n                // Android 低版本系统上有(Application)context.getApplicationContext()的代码\n                // 这里返回宿主的Application作为ApplicationContext\n                // https://cs.android.com/android/platform/superproject/+/android-4.0.1_r1:frameworks/base/core/java/android/view/Window.java;l=471\n                return getApplication().getBaseContext().getApplicationContext();\n            }\n        });\n\n\n        ViewGroup mItemViewGroup = UiUtil.setAlertDialogBuilderContentView(builder);\n\n\n        AlertDialog dialog = builder.show();\n\n        dialog.setOwnerActivity(this);\n        Activity ownerActivity = dialog.getOwnerActivity();\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"ownerActivityIsThis\",\n                        \"ownerActivityIsThis\",\n                        Boolean.toString(ownerActivity == this)\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/dialog/TestDialog.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.support.annotation.NonNull;\nimport android.view.Window;\n\npublic class TestDialog extends Dialog {\n\n    public TestDialog(@NonNull Context context) {\n        super(context);\n\n        getWindow().requestFeature(Window.FEATURE_NO_TITLE);\n        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/dialog/TestDialogActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.dialog;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.res.Configuration;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestDialogActivity extends Activity {\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        TestDialog dialog = new TestDialog(new ContextWrapper(this) {\n            @Override\n            public Context getApplicationContext() {\n                // Android 低版本系统上有(Application)context.getApplicationContext()的代码\n                // 这里返回宿主的Application作为ApplicationContext\n                // https://cs.android.com/android/platform/superproject/+/android-4.0.1_r1:frameworks/base/core/java/android/view/Window.java;l=471\n                return getApplication().getBaseContext().getApplicationContext();\n            }\n        });\n\n        dialog.setOwnerActivity(this);\n        Activity ownerActivity = dialog.getOwnerActivity();\n\n        ViewGroup mItemViewGroup = UiUtil.setDialogContentView(dialog);\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"ownerActivityIsThis\",\n                        \"ownerActivityIsThis\",\n                        Boolean.toString(ownerActivity == this)\n                )\n        );\n\n        dialog.show();\n    }\n\n    @Override\n    protected void attachBaseContext(Context newBase) {\n        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            Configuration configuration = new Configuration();\n            Context context = newBase.createConfigurationContext(configuration);\n            super.attachBaseContext(context);\n        } else {\n            super.attachBaseContext(newBase);\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/BaseFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Fragment;\n\npublic class BaseFragment extends Fragment {\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/FragmentStartedActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.SimpleIdlingResource;\n\npublic class FragmentStartedActivity extends Activity {\n\n    public static SimpleIdlingResource sIdlingResource = new SimpleIdlingResource() {\n        @Override\n        public String getName() {\n            return \"FragmentStartedActivity\";\n        }\n    };\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        Button button = new Button(this);\n        button.setText(\"finish\");\n        button.setTag(\"finish_button\");\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FragmentStartedActivity.this.finish();\n            }\n        });\n\n        setContentView(button);\n\n        sIdlingResource.setIdleState(true);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/OnlyOverrideActivityMethodBaseFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.util.AttributeSet;\n\npublic class OnlyOverrideActivityMethodBaseFragment extends Fragment {\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/OnlyOverrideContextMethodBaseFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.util.AttributeSet;\n\npublic class OnlyOverrideContextMethodBaseFragment extends Fragment {\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/ProgrammaticallyAddFragmentActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\npublic class ProgrammaticallyAddFragmentActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_fragment_activity);\n\n        String fragmentType = getIntent().getStringExtra(\"FragmentType\");\n        addFragmentProgrammatically(fragmentType);\n    }\n\n    private void addFragmentProgrammatically(String fragmentType) {\n\n\n        TestFragment testFragment;\n        switch (fragmentType) {\n            case \"TestNormalFragment\":\n                testFragment = new TestNormalFragment();\n                break;\n            case \"TestSubFragment\":\n                testFragment = new TestSubFragment();\n                break;\n            case \"TestBaseFragment\":\n                testFragment = new SubTestBaseFragment();\n                break;\n            case \"TestDialogFragment\":\n                testFragment = new TestDialogFragment();\n                break;\n            case \"OnlyOverrideActivityMethodBaseFragment\":\n                testFragment = new TestSubOnlyOverrideOnAttachActivityFragment();\n                break;\n            case \"OnlyOverrideContextMethodBaseFragment\":\n                testFragment = new TestSubOnlyOverrideOnAttachContextFragment();\n                break;\n            default:\n                throw new IllegalArgumentException(\"fragmentType不识别：\" + fragmentType);\n        }\n        testFragment.setTestArguments(\"addFragmentProgrammatically\");\n        getFragmentManager().beginTransaction()\n                .add(R.id.fragment_container, (Fragment) testFragment, \"TestFragmentTag\")\n                .commit();\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/SubTestBaseFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\n\n/**\n * 测试子类Override了onAttach的情况下TestBaseFragment是否表现正常\n */\npublic class SubTestBaseFragment extends TestBaseFragment {\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TargetFragmentTestActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TargetFragmentTestActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        Fragment fragA = new TestNormalFragment();\n        Fragment fragB = new TestNormalFragment();\n        getFragmentManager().beginTransaction()\n                .add(fragA, \"fragA\")\n                .add(fragB, \"fragB\")\n                .commit();\n\n        fragA.setTargetFragment(fragB, 47);\n\n        String tagOfTarget = fragA.getTargetFragment().getTag();\n        int targetRequestCode = fragA.getTargetRequestCode();\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"tagOfTarget\",\n                        \"tagOfTarget\",\n                        String.valueOf(tagOfTarget)\n                )\n        );\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"targetRequestCode\",\n                        \"targetRequestCode\",\n                        String.valueOf(targetRequestCode)\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestBaseFragment.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\n@SuppressWarnings(\"deprecation\")\npublic class TestBaseFragment extends Fragment implements TestFragment {\n    public TestBaseFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestDialogFragment.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.DialogFragment;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\n@SuppressWarnings(\"deprecation\")\npublic class TestDialogFragment extends DialogFragment implements TestFragment {\n    public TestDialogFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\ninterface TestFragment {\n    void setTestArguments(String msg);\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestFragmentCommonLogic.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.RequiresApi;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\n@SuppressLint(\"SetTextI18n\")\nclass TestFragmentCommonLogic {\n\n    final private Fragment fragment;\n\n    public TestFragmentCommonLogic(Fragment fragment) {\n        this.fragment = fragment;\n    }\n\n    void setTestArguments(String msg) {\n        Bundle bundle = new Bundle();\n        bundle.putString(\"msg\", msg);\n        if (fragment.getArguments() != null) {\n            fragment.getArguments().putAll(bundle);\n        } else {\n            fragment.setArguments(bundle);\n        }\n    }\n\n    View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        LinearLayout rootView = (LinearLayout) inflater.inflate(R.layout.layout_fragment_test, null, false);\n\n        addTestArgumentsView(rootView);\n\n        addFragmentStartActivityView(rootView);\n        addFragmentStartActivityWithOptionsView(rootView);\n\n        addAttachContextView(rootView);\n        addAttachActivityView(rootView);\n\n        addInflateContextView(rootView);\n        addInflateActivityView(rootView);\n\n        addGetActivityView(rootView);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            addGetContextView(rootView);\n            addGetHostView(rootView);\n        }\n\n        return rootView;\n    }\n\n    private void addTestArgumentsView(LinearLayout rootView) {\n        TextView textView = rootView.findViewById(R.id.tv_msg);\n        textView.setTag(\"TestFragmentTextView\");\n        Bundle bundle = fragment.getArguments();\n        if (bundle != null) {\n            String msg = bundle.getString(\"msg\");\n            if (!TextUtils.isEmpty(msg)) {\n                textView.setText(msg);\n            }\n        }\n    }\n\n    private void addFragmentStartActivityView(LinearLayout rootView) {\n        Button fragmentStartActivity = new Button(rootView.getContext());\n        fragmentStartActivity.setText(\"fragmentStartActivity\");\n        fragmentStartActivity.setTag(\"fragmentStartActivity\");\n        fragmentStartActivity.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                fragmentStartActivity();\n            }\n        });\n        rootView.addView(fragmentStartActivity);\n    }\n\n    private void fragmentStartActivity() {\n        Intent intent = new Intent(fragment.getActivity(), FragmentStartedActivity.class);\n        fragment.startActivity(intent);\n        FragmentStartedActivity.sIdlingResource.setIdleState(false);\n    }\n\n    private void addFragmentStartActivityWithOptionsView(LinearLayout rootView) {\n        Button fragmentStartActivityWithOptions = new Button(rootView.getContext());\n        fragmentStartActivityWithOptions.setText(\"fragmentStartActivityWithOptions\");\n        fragmentStartActivityWithOptions.setTag(\"fragmentStartActivityWithOptions\");\n        fragmentStartActivityWithOptions.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                    fragmentStartActivityWithOptions();\n                }\n            }\n        });\n        rootView.addView(fragmentStartActivityWithOptions);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    private void fragmentStartActivityWithOptions() {\n        Intent intent = new Intent(fragment.getActivity(), FragmentStartedActivity.class);\n        ActivityOptions activityOptions = ActivityOptions.makeBasic();\n        fragment.startActivity(intent, activityOptions.toBundle());\n        FragmentStartedActivity.sIdlingResource.setIdleState(false);\n    }\n\n    private void addAttachContextView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"AttachContextView\");\n        if (attachContext == null) {\n            textView.setText(\"attachContext == null\");\n        } else {\n            textView.setText(attachContext.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    private void addAttachActivityView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"AttachActivityView\");\n        if (attachActivity == null) {\n            textView.setText(\"attachActivity == null\");\n        } else {\n            textView.setText(attachActivity.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    private Context attachContext;\n    private Activity attachActivity;\n\n    void onAttach(Context context) {\n        attachContext = context;\n    }\n\n    void onAttach(Activity activity) {\n        attachActivity = activity;\n    }\n\n    void onDetach() {\n        attachContext = null;\n        attachActivity = null;\n        inflateContext = null;\n        inflateActivity = null;\n    }\n\n    private void addInflateContextView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"InflateContextView\");\n        if (inflateContext == null) {\n            textView.setText(\"inflateContext == null\");\n        } else {\n            textView.setText(inflateContext.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    private void addInflateActivityView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"InflateActivityView\");\n        if (inflateActivity == null) {\n            textView.setText(\"inflateActivity == null\");\n        } else {\n            textView.setText(inflateActivity.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    private Context inflateContext;\n    private Activity inflateActivity;\n\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        inflateContext = context;\n    }\n\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        inflateActivity = activity;\n    }\n\n    private void addGetActivityView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"GetActivityView\");\n        Activity activity = fragment.getActivity();\n        if (activity == null) {\n            textView.setText(\"activity == null\");\n        } else {\n            textView.setText(activity.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    private void addGetContextView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"GetContextView\");\n        Context context = fragment.getContext();\n        if (context == null) {\n            textView.setText(\"context == null\");\n        } else {\n            textView.setText(context.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n\n    @RequiresApi(api = Build.VERSION_CODES.M)\n    private void addGetHostView(LinearLayout rootView) {\n        TextView textView = new TextView(rootView.getContext());\n        textView.setTag(\"GetHostView\");\n        Object host = fragment.getHost();\n        if (host == null) {\n            textView.setText(\"host == null\");\n        } else {\n            textView.setText(host.getClass().getCanonicalName());\n        }\n        rootView.addView(textView);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestNormalFragment.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\n@SuppressWarnings(\"deprecation\")\npublic class TestNormalFragment extends Fragment implements TestFragment {\n\n    public TestNormalFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n\n    /**\n     * 测试Fragment override getContext的场景\n     */\n    @Override\n    public Context getContext() {\n        return super.getContext();\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\npublic class TestSubFragment extends BaseFragment implements TestFragment {\n    public TestSubFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachActivityFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\npublic class TestSubOnlyOverrideOnAttachActivityFragment extends OnlyOverrideActivityMethodBaseFragment implements TestFragment {\n    public TestSubOnlyOverrideOnAttachActivityFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/TestSubOnlyOverrideOnAttachContextFragment.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport androidx.test.espresso.IdlingRegistry;\n\npublic class TestSubOnlyOverrideOnAttachContextFragment extends OnlyOverrideContextMethodBaseFragment implements TestFragment {\n    public TestSubOnlyOverrideOnAttachContextFragment() {\n        setArguments(new Bundle());//低版本系统上不允许attach后再setArguments\n    }\n\n    final TestFragmentCommonLogic commonLogic = new TestFragmentCommonLogic(this);\n\n    public void setTestArguments(String msg) {\n        commonLogic.setTestArguments(msg);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {\n        return commonLogic.onCreateView(inflater, container, savedInstanceState);\n    }\n\n\n    @Override\n    public void onAttach(Context context) {\n        super.onAttach(context);\n        commonLogic.onAttach(context);\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        commonLogic.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        commonLogic.onDetach();\n        super.onDetach();\n    }\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        IdlingRegistry.getInstance().register(FragmentStartedActivity.sIdlingResource);\n    }\n\n    @Override\n    public void onDestroy() {\n        IdlingRegistry.getInstance().unregister(FragmentStartedActivity.sIdlingResource);\n        super.onDestroy();\n    }\n\n    @Override\n    public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(context, attrs, savedInstanceState);\n        commonLogic.onInflate(context, attrs, savedInstanceState);\n    }\n\n    @Override\n    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {\n        super.onInflate(activity, attrs, savedInstanceState);\n        commonLogic.onInflate(activity, attrs, savedInstanceState);\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/fragment/XmlAddFragmentActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\npublic class XmlAddFragmentActivity extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        String fragmentType = getIntent().getStringExtra(\"FragmentType\");\n        int xmlId;\n        switch (fragmentType) {\n            case \"TestNormalFragment\":\n                xmlId = R.layout.layout_xml_add_normal_fragment_activity;\n                break;\n            case \"TestSubFragment\":\n                xmlId = R.layout.layout_xml_add_sub_fragment_activity;\n                break;\n            case \"TestBaseFragment\":\n                xmlId = R.layout.layout_xml_add_base_fragment_activity;\n                break;\n            default:\n                throw new IllegalArgumentException(\"fragmentType不识别：\" + fragmentType);\n        }\n\n        setContentView(xmlId);\n    }\n\n    @Override\n    public void onAttachFragment(Fragment fragment) {\n        super.onAttachFragment(fragment);\n        if (\"TestFragmentTag\".equals(fragment.getTag())) {\n            TestFragment testFragment = (TestFragment) fragment;\n            testFragment.setTestArguments(\"addFragmentWithXml\");\n        }\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/instrumentation/MyInstrumentation.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.instrumentation;\n\nimport android.app.Instrumentation;\n\npublic class MyInstrumentation extends Instrumentation {\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/instrumentation/TestInstrumentationActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.instrumentation;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Instrumentation;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.PersistableBundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestInstrumentationActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        boolean newApplicationSuccess = false;\n        try {\n            Application app = Instrumentation.newApplication(Application.class, getApplicationContext());\n            newApplicationSuccess = true;\n        } catch (Exception ignored) {\n        }\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"newApplicationSuccess\",\n                        \"newApplicationSuccess\",\n                        Boolean.toString(newApplicationSuccess)\n                )\n        );\n\n        boolean callActivityOnDestroySuccess = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            TestInstrumentationActivity testActivity = new TestInstrumentationActivity();\n            myInstrumentation.callActivityOnDestroy(testActivity);\n        } catch (NullPointerException ignored) {\n            callActivityOnDestroySuccess = true;\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"callActivityOnDestroySuccess\",\n                        \"callActivityOnDestroySuccess\",\n                        Boolean.toString(callActivityOnDestroySuccess)\n                )\n        );\n\n\n        boolean newApplicationSuccess1 = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            Application app = myInstrumentation.newApplication(myInstrumentation.getClass().getClassLoader(),\n                    Application.class.getName(),\n                    getApplicationContext());\n            newApplicationSuccess1 = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(this,\n                        \"newApplicationSuccess1\",\n                        \"newApplicationSuccess1\",\n                        Boolean.toString(newApplicationSuccess1))\n        );\n\n\n        boolean newShadowActivitySuccess = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            myInstrumentation.newActivity(myInstrumentation.getClass().getClassLoader(),\n                    TestInstrumentationActivity.class.getName(),\n                    null);\n            newShadowActivitySuccess = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(this,\n                        \"newShadowActivitySuccess\",\n                        \"newShadowActivitySuccess\",\n                        Boolean.toString(newShadowActivitySuccess))\n        );\n\n        boolean callApplicationOnCreateSuccess = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            myInstrumentation.callApplicationOnCreate(getApplication());\n            callApplicationOnCreateSuccess = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"callApplicationOnCreateSuccess\",\n                        \"callApplicationOnCreateSuccess\",\n                        Boolean.toString(callApplicationOnCreateSuccess)\n                )\n        );\n\n        boolean callActivityOnCreateSuccess = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            myInstrumentation.callActivityOnCreate(this, new Bundle());\n            callActivityOnCreateSuccess = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"callActivityOnCreateSuccess\",\n                        \"callActivityOnCreateSuccess\",\n                        Boolean.toString(callActivityOnCreateSuccess)\n                )\n        );\n\n        boolean callActivityOnCreateSuccess1 = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                myInstrumentation.callActivityOnCreate(this, new Bundle(), new PersistableBundle());\n            }\n            callActivityOnCreateSuccess1 = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"callActivityOnCreateSuccess1\",\n                        \"callActivityOnCreateSuccess1\",\n                        Boolean.toString(callActivityOnCreateSuccess1)\n                )\n        );\n\n        boolean execStartActivitySuccess = false;\n        try {\n            MyInstrumentation myInstrumentation = new MyInstrumentation();\n            // 这里是UnsupportedAppUsage的,无法测试\n//            myInstrumentation.execStartActivity(this, new Bundle(), new PersistableBundle());\n            execStartActivitySuccess = true;\n        } catch (Exception ignore) {\n        }\n\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"execStartActivitySuccess\",\n                        \"execStartActivitySuccess\",\n                        Boolean.toString(execStartActivitySuccess)\n                )\n        );\n    }\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/interfaces/TestHostInterfaceActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.interfaces;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestHostInterfaceActivity extends Activity {\n\n    private static final String BASE_PACKAGE = \"com.tencent.shadow.test.lib.plugin_use_host_code_lib\";\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        loadClass(\"in_whitelist\",//直接在白名单中的类\n                BASE_PACKAGE + \".interfaces.HostTestInterface\");\n\n        loadClass(\"not_in_whitelist_other_package\",//不再白名单中的其他包中的类\n                BASE_PACKAGE + \".other.HostOtherInterface\");\n\n        loadClass(\"not_in_whitelist_sub_package\",//属于白名单中包的子包中的类\n                BASE_PACKAGE + \".interfaces.subpackage.Foo\");\n    }\n\n    private void loadClass(String tag, String className) {\n\n        ClassLoader classLoader = TestHostInterfaceActivity.class.getClassLoader();\n\n        boolean loadSuccess;\n        try {\n            classLoader.loadClass(className);\n            loadSuccess = true;\n        } catch (ClassNotFoundException e) {\n            loadSuccess = false;\n        }\n\n        makeItem(\"loadClass:\" + className, \"TAG_loadClass_\" + tag,\n                Boolean.toString(loadSuccess)\n        );\n    }\n\n    private void makeItem(\n            String labelText,\n            final String viewTag,\n            String value\n    ) {\n        ViewGroup item = UiUtil.makeItem(this, labelText, viewTag, value);\n        mItemViewGroup.addView(item);\n    }\n}"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/packagemanager/TestPackageManagerActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.packagemanager;\n\nimport static android.content.pm.PackageManager.GET_META_DATA;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ProviderInfo;\nimport android.content.pm.ResolveInfo;\nimport android.content.pm.ServiceInfo;\nimport android.content.pm.VersionedPackage;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.service.TestService;\n\nimport java.util.List;\n\npublic class TestPackageManagerActivity extends Activity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ViewGroup viewGroup = UiUtil.setActivityContentView(this);\n\n        getApplicationInfo(viewGroup);\n        getActivityInfo(viewGroup);\n        getServiceInfo(viewGroup);\n        getPackageInfo(viewGroup);\n        queryContentProviders(viewGroup);\n        resolveActivityByExplicitIntent(viewGroup);\n    }\n\n    private void getApplicationInfo(ViewGroup viewGroup) {\n        String className;\n        String nativeLibraryDir;\n        String metaData;\n        try {\n            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), GET_META_DATA);\n            className = applicationInfo.className;\n            nativeLibraryDir = applicationInfo.nativeLibraryDir;\n            metaData = applicationInfo.metaData != null ? applicationInfo.metaData.getString(\"test_meta\") : null;\n        } catch (PackageManager.NameNotFoundException e) {\n            className = nativeLibraryDir = metaData = \"NameNotFoundException\";\n        }\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getApplicationInfo/className\",\n                        \"getApplicationInfo/className\",\n                        className\n                )\n        );\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getApplicationInfo/nativeLibraryDir\",\n                        \"getApplicationInfo/nativeLibraryDir\",\n                        nativeLibraryDir\n                )\n        );\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getApplicationInfo/metaData\",\n                        \"getApplicationInfo/metaData\",\n                        metaData\n                )\n        );\n    }\n\n    private void getActivityInfo(ViewGroup viewGroup) {\n        String name;\n        String packageName;\n        try {\n            ActivityInfo activityInfo = getPackageManager().getActivityInfo(new ComponentName(this, this.getClass()), 0);\n            name = activityInfo.name;\n            packageName = activityInfo.packageName;\n        } catch (PackageManager.NameNotFoundException e) {\n            name = packageName = \"NameNotFoundException\";\n        }\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getActivityInfo/name\",\n                        \"getActivityInfo/name\",\n                        name\n                )\n        );\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getActivityInfo/packageName\",\n                        \"getActivityInfo/packageName\",\n                        packageName\n                )\n        );\n    }\n\n    private void getServiceInfo(ViewGroup viewGroup) {\n        String name;\n        String packageName;\n        try {\n            ServiceInfo serviceInfo = getPackageManager().getServiceInfo(new ComponentName(this, TestService.class), 0);\n            name = serviceInfo.name;\n            packageName = serviceInfo.packageName;\n        } catch (PackageManager.NameNotFoundException e) {\n            name = packageName = \"NameNotFoundException\";\n        }\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getServiceInfo/name\",\n                        \"getServiceInfo/name\",\n                        name\n                )\n        );\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getServiceInfo/packageName\",\n                        \"getServiceInfo/packageName\",\n                        packageName\n                )\n        );\n    }\n\n    private void getPackageInfo(ViewGroup viewGroup) {\n        String versionName;\n        String versionCode;\n        try {\n            PackageManager packageManager = getPackageManager();\n            String packageName = getPackageName();\n            PackageInfo packageInfo;\n\n            //'getPackageInfo(java.lang.String, int)' is deprecated as of API 33 (\"Tiramisu\"; Android 13.0)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                packageInfo = packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));\n\n                //再测试调用一下传VersionedPackage的方法\n                VersionedPackage versionedPackage = new VersionedPackage(packageInfo.packageName, packageInfo.getLongVersionCode());\n                packageInfo = packageManager.getPackageInfo(versionedPackage, PackageManager.PackageInfoFlags.of(0));\n            } else {\n                packageInfo = packageManager.getPackageInfo(packageName, 0);\n            }\n\n            versionName = packageInfo.versionName;\n            versionCode = Integer.toString(packageInfo.versionCode);\n        } catch (PackageManager.NameNotFoundException e) {\n            versionName = versionCode = \"NameNotFoundException\";\n        }\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getPackageInfo/versionName\",\n                        \"getPackageInfo/versionName\",\n                        versionName\n                )\n        );\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"getPackageInfo/versionCode\",\n                        \"getPackageInfo/versionCode\",\n                        versionCode\n                )\n        );\n    }\n\n    private void queryContentProviders(ViewGroup viewGroup) {\n        PackageManager packageManager = getPackageManager();\n        ApplicationInfo applicationInfo = getApplicationInfo();\n        String processName = applicationInfo.processName;\n        int uid = applicationInfo.uid;\n        List<ProviderInfo> providerInfos = packageManager.queryContentProviders(processName, uid, PackageManager.MATCH_ALL);\n\n        String size = providerInfos.size() > 0 ? \">0\" : \"0\";\n\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"queryContentProviders/size\",\n                        \"queryContentProviders/size\",\n                        size\n                )\n        );\n    }\n\n    private void resolveActivityByExplicitIntent(ViewGroup viewGroup) {\n        PackageManager packageManager = getPackageManager();\n        Intent intent = new Intent(this, this.getClass());\n        ResolveInfo resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);\n\n        String name;\n        if (resolveInfo != null && resolveInfo.activityInfo != null) {\n            name = resolveInfo.activityInfo.name;\n        } else {\n            name = \"\";\n        }\n        viewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"resolveActivity/explicit\",\n                        \"resolveActivity/explicit\",\n                        name\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/provider/TestDBContentProviderActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.provider;\n\nimport android.app.Activity;\nimport android.content.ContentValues;\nimport android.database.ContentObserver;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\npublic class TestDBContentProviderActivity extends Activity {\n\n    private static final String TAG = \"ContentProviderActivity\";\n\n    private TextView mTextView;\n\n    private Handler mHandler = new Handler();\n    private ContentObserver mObserver = new ContentObserver(mHandler) {\n        @Override\n        public void onChange(boolean selfChange, Uri uri) {\n            super.onChange(selfChange, uri);\n            Log.d(TAG, uri + \" onChange\");\n            Toast.makeText(TestDBContentProviderActivity.this, uri + \" onChange\", Toast.LENGTH_SHORT).show();\n        }\n    };\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_provider_db);\n\n        mTextView = findViewById(R.id.text);\n\n        getContentResolver().registerContentObserver(TestProviderInfo.TestEntry.CONTENT_URI,\n                false, mObserver);\n    }\n\n    public void insert(View view) {\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis());\n        getContentResolver().insert(TestProviderInfo.TestEntry.CONTENT_URI, contentValues);\n\n        query(view);\n    }\n\n    public void query(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI, null, null, null, null);\n        if (cursor != null) {\n            StringBuilder s = new StringBuilder();\n            while (cursor.moveToNext()) {\n                long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n                String name = cursor.getString(cursor.getColumnIndex(TestProviderInfo.TestEntry.COLUMN_NAME));\n                s.append(\"id:\").append(id).append(\" name:\").append(name).append(\" \\n\");\n            }\n            mTextView.setText(s);\n            cursor.close();\n        } else {\n            Toast.makeText(this, \"请先插入数据\", Toast.LENGTH_SHORT).show();\n        }\n\n    }\n\n    public void update(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI,\n                null, null, null, null);\n        int count = cursor != null ? cursor.getCount() : 0;\n        if (count > 0) {\n            cursor.moveToFirst();\n            ContentValues contentValues = new ContentValues();\n            contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"name \" + System.currentTimeMillis());\n\n            long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n            getContentResolver().update(TestProviderInfo.TestEntry.CONTENT_URI, contentValues,\n                    TestProviderInfo.TestEntry._ID + \" = ?\",\n                    new String[]{String.valueOf(id)});\n        }\n        if (cursor != null) {\n            cursor.close();\n        }\n\n        query(view);\n    }\n\n    public void delete(View view) {\n        Cursor cursor = getContentResolver().query(TestProviderInfo.TestEntry.CONTENT_URI,\n                null, null, null, null);\n        int count = cursor != null ? cursor.getCount() : 0;\n        if (count > 0) {\n            cursor.moveToFirst();\n            long id = cursor.getLong(cursor.getColumnIndex(TestProviderInfo.TestEntry._ID));\n            getContentResolver().delete(TestProviderInfo.TestEntry.CONTENT_URI,\n                    TestProviderInfo.TestEntry._ID + \" = ?\",\n                    new String[]{String.valueOf(id)});\n        }\n        if (cursor != null) {\n            cursor.close();\n        }\n\n        query(view);\n    }\n\n    public void bulkInsert(View view) {\n        ContentValues[] values = new ContentValues[3];\n        ContentValues contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis());\n        values[0] = contentValues;\n\n        contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis() + 5);\n        values[1] = contentValues;\n\n        contentValues = new ContentValues();\n        contentValues.put(TestProviderInfo.TestEntry.COLUMN_NAME, \"test\");\n        contentValues.put(TestProviderInfo.TestEntry._ID, System.currentTimeMillis() + 10);\n        values[2] = contentValues;\n\n        getContentResolver().bulkInsert(TestProviderInfo.TestEntry.CONTENT_URI, values);\n\n        query(view);\n    }\n\n    public void call(View view) {\n        Bundle beauty = getContentResolver().call(TestProviderInfo.TestEntry.CONTENT_URI, \"getBeauty\", \"18\", null);\n        if (beauty != null) {\n            Toast.makeText(this, \"get beauty who name is \" + beauty.getString(\"name\"), Toast.LENGTH_LONG).show();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        getContentResolver().unregisterContentObserver(mObserver);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/provider/TestDBHelper.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.provider;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\n\npublic class TestDBHelper extends SQLiteOpenHelper {\n    private static final int DATABASE_VERSION = 1;\n    private static final String DATABASE_NAME = \"shadow.db\";\n\n    public TestDBHelper(Context context) {\n        super(context, DATABASE_NAME, null, DATABASE_VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        final String SQL_CREATE_CONTACT_TABLE = \"CREATE TABLE \" + TestProviderInfo.TestEntry.TABLE_NAME + \"( \"\n                + TestProviderInfo.TestEntry._ID + \" TEXT PRIMARY KEY, \"\n                + TestProviderInfo.TestEntry.COLUMN_NAME + \" TEXT NOT NULL );\";\n\n        db.execSQL(SQL_CREATE_CONTACT_TABLE);\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.execSQL(\"DROP TABLE IF EXISTS \" + TestProviderInfo.TestEntry.TABLE_NAME);\n        onCreate(db);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/provider/TestFileProviderActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.provider;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.provider.MediaStore;\nimport android.support.v4.content.FileProvider;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport com.tencent.shadow.test.plugin.general_cases.BuildConfig;\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\nimport java.io.File;\n\npublic class TestFileProviderActivity extends Activity {\n\n    private static final int REQUEST_CODE = 1001;\n\n    private ImageView mImageView;\n    private File mFile;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_test_file_provider);\n        mImageView = findViewById(R.id.photo);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            requestPermissions(new String[]{Manifest.permission.CAMERA}, 1001);\n        }\n\n        findViewById(R.id.go_take_photo).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                String fileName = String.valueOf(System.currentTimeMillis());\n                String filePath = getFilesDir() + \"/images/\" + fileName + \".jpg\";\n                mFile = new File(filePath);\n                if (!mFile.getParentFile().exists()) {\n                    mFile.getParentFile().mkdir();\n                }\n\n                Uri contentUri;\n                if (targetSdkVersion() >= Build.VERSION_CODES.N) {\n                    contentUri = FileProvider.getUriForFile(TestFileProviderActivity.this,\n                            BuildConfig.APPLICATION_ID + \".general_cases.fileprovider\", mFile);\n//                    contentUri = Uri.parse(\"content://com.tencent.shadow.contentprovider.authority/com.tencent.shadow.test.plugin.general_cases.lib.gallery.fileprovider\" +\n//                            \"/name/data/data/com.tencent.shadow.test.hostapp/files/images/1548417832706.jpg\");\n                } else {\n                    contentUri = Uri.fromFile(mFile);\n                }\n                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n                intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);\n                startActivityForResult(intent, REQUEST_CODE);\n            }\n        });\n    }\n\n    private int targetSdkVersion() {\n        return getApplicationContext().getApplicationInfo().targetSdkVersion;\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {\n            setPic();\n        }\n    }\n\n    private void setPic() {\n        // Get the dimensions of the View\n        int targetW = mImageView.getWidth();\n        int targetH = mImageView.getHeight();\n\n        // Get the dimensions of the bitmap\n        BitmapFactory.Options bmOptions = new BitmapFactory.Options();\n        bmOptions.inJustDecodeBounds = true;\n        BitmapFactory.decodeFile(mFile.getAbsolutePath(), bmOptions);\n        int photoW = bmOptions.outWidth;\n        int photoH = bmOptions.outHeight;\n\n        // Determine how much to scale down the image\n        int scaleFactor = Math.min(photoW / targetW, photoH / targetH);\n\n        // Decode the image file into a Bitmap sized to fill the View\n        bmOptions.inJustDecodeBounds = false;\n        bmOptions.inSampleSize = scaleFactor;\n        bmOptions.inPurgeable = true;\n\n        Bitmap bitmap = BitmapFactory.decodeFile(mFile.getAbsolutePath(), bmOptions);\n        mImageView.setImageBitmap(bitmap);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/provider/TestProvider.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.provider;\n\nimport android.content.ContentProvider;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.UriMatcher;\nimport android.content.pm.ProviderInfo;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\n/**\n * TestProvider\n * Created by 90Chris on 2016/5/1.\n */\npublic class TestProvider extends ContentProvider {\n    private TestDBHelper mOpenHelper;\n\n    @Override\n    public boolean onCreate() {\n        mOpenHelper = new TestDBHelper(getContext());\n        return true;\n    }\n\n    @Override\n    public void attachInfo(Context context, ProviderInfo info) {\n        super.attachInfo(context, info);\n        //用于测试是否读取了Manifest中的grantUriPermissions值,在实际生产中这里并不需要抛出异常\n        if (!info.grantUriPermissions) {\n            throw new IllegalStateException(\"读取ProviderInfo.grantUriPermissions失败\");\n        }\n    }\n\n\n    @Nullable\n    @Override\n    public String getType(Uri uri) {\n        return null;\n    }\n\n    @Nullable\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();\n\n        Cursor cursor = null;\n        switch (buildUriMatcher().match(uri)) {\n            case TEST:\n                cursor = db.query(TestProviderInfo.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);\n                break;\n        }\n\n        return cursor;\n    }\n\n    @SuppressWarnings(\"ConstantConditions\")\n    @Nullable\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        Uri returnUri;\n        long _id;\n        switch (buildUriMatcher().match(uri)) {\n            case TEST:\n                _id = db.insert(TestProviderInfo.TestEntry.TABLE_NAME, null, values);\n                if (_id > 0) {\n                    returnUri = TestProviderInfo.TestEntry.buildUri(_id);\n                    getContext().getContentResolver().notifyChange(returnUri, null);\n                } else\n                    throw new android.database.SQLException(\"Failed to insert row into \" + uri);\n                break;\n            default:\n                throw new android.database.SQLException(\"Unknown uri: \" + uri);\n        }\n        return returnUri;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        int result = db.delete(TestProviderInfo.TestEntry.TABLE_NAME, selection, selectionArgs);\n        if (result > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return result;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n        int result = db.update(TestProviderInfo.TestEntry.TABLE_NAME, values, selection, selectionArgs);\n        if (result > 0) {\n            getContext().getContentResolver().notifyChange(uri, null);\n        }\n        return result;\n    }\n\n    public Bundle call(@NonNull String method, String arg, @Nullable Bundle extras) {\n        switch (method) {\n            case \"getBeauty\":\n                Bundle bundle = new Bundle();\n                bundle.putString(\"name\", \"Anne Hathaway\");\n                return bundle;\n        }\n        return null;\n    }\n\n    private final static int TEST = 100;\n\n    static UriMatcher buildUriMatcher() {\n        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);\n        final String authority = TestProviderInfo.CONTENT_AUTHORITY;\n\n        matcher.addURI(authority, TestProviderInfo.PATH_TEST, TEST);\n\n        return matcher;\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/provider/TestProviderInfo.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.provider;\n\nimport android.content.ContentUris;\nimport android.net.Uri;\nimport android.provider.BaseColumns;\n\nimport com.tencent.shadow.test.plugin.general_cases.BuildConfig;\n\n\npublic class TestProviderInfo {\n\n    protected static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + \".provider.test\";\n    ;\n    protected static final Uri BASE_CONTENT_URI = Uri.parse(\"content://\" + CONTENT_AUTHORITY);\n\n    protected static final String PATH_TEST = \"test\";\n\n    public static final class TestEntry implements BaseColumns {\n\n        public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_TEST).build();\n\n        protected static Uri buildUri(long id) {\n            return ContentUris.withAppendedId(CONTENT_URI, id);\n        }\n\n        protected static final String TABLE_NAME = \"TestProviderInfo\";\n\n        public static final String COLUMN_NAME = \"name\";\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/receiver/BroadCastHelper.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.receiver;\n\nimport android.content.Context;\nimport android.content.Intent;\n\npublic class BroadCastHelper {\n\n\n    private static Notify notify;\n\n\n    public static void setNotify(Notify notify) {\n        BroadCastHelper.notify = notify;\n    }\n\n    public static void notify(Intent intent, Context context) {\n        notify.onReceiver(intent, context);\n    }\n\n    interface Notify {\n        void onReceiver(Intent intent, Context context);\n    }\n\n\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/receiver/MyReceiver.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.receiver;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.widget.Toast;\n\npublic class MyReceiver extends BroadcastReceiver {\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        String msg = intent.getStringExtra(\"msg\");\n        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();\n\n        BroadCastHelper.notify(intent, context);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/receiver/ReceiverWithoutAction.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.receiver;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\n/**\n * 测试插件中包含无Action的Receiver能否正常启动\n */\npublic class ReceiverWithoutAction extends BroadcastReceiver {\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/receiver/TestReceiverActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.receiver;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.WithIdlingResourceActivity;\n\npublic class TestReceiverActivity extends WithIdlingResourceActivity {\n\n    private final static String INTENT_NORMAL_ACTION = \"com.tencent.test.normal.action\";\n    private final static String INTENT_DYNAMIC_ACTION = \"com.tencent.test.action.DYNAMIC\";\n\n    private final static String MSG_NORMAL = \"收到测试静态广播发送\";\n    private final static String MSG_DYNAMIC = \"收到动态动态广播发送\";\n\n\n    private TextView mTextView;\n\n    private BroadcastReceiver mBroadcastReceiver;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_receiver);\n\n        mTextView = findViewById(R.id.content);\n\n        BroadCastHelper.setNotify(new BroadCastHelper.Notify() {\n            @Override\n            public void onReceiver(Intent intent, Context context) {\n                mIdlingResource.setIdleState(true);\n\n                boolean isShadowContext = false;\n                try {\n                    Class clazz = Class.forName(\"com.tencent.shadow.core.runtime.ShadowContext\");\n                    isShadowContext = clazz.isAssignableFrom(context.getClass());\n                } catch (ClassNotFoundException e) {\n                    e.printStackTrace();\n                }\n                mTextView.setText(String.format(\"action:%s msg:%s isShadowContext:%s\", intent.getAction(), intent.getStringExtra(\"msg\"), isShadowContext));\n            }\n        });\n\n        mBroadcastReceiver = new DynamicBroadcastReceiver();\n        registerReceiver(mBroadcastReceiver, new IntentFilter(INTENT_DYNAMIC_ACTION));\n    }\n\n    public void TestNormalBraodcast(View view) {\n        mIdlingResource.setIdleState(false);\n        Intent intent = new Intent(INTENT_NORMAL_ACTION);\n        intent.putExtra(\"msg\", MSG_NORMAL);\n        intent.putExtra(\"custom_parcel\", new CustomParcel());\n        sendBroadcast(intent);\n    }\n\n    public void TestDynamicBraodcast(View view) {\n        mIdlingResource.setIdleState(false);\n        Intent intent = new Intent(INTENT_DYNAMIC_ACTION);\n        intent.putExtra(\"msg\", MSG_DYNAMIC);\n        sendBroadcast(intent);\n    }\n\n    public void TestUnregisterDynamicBraodcast(View view) {\n        mTextView.setText(\"\");\n        unregisterReceiver(mBroadcastReceiver);\n        TestDynamicBraodcast(view);\n\n        new Handler().postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                mIdlingResource.setIdleState(true);\n            }\n        }, 2000);\n    }\n\n    private class DynamicBroadcastReceiver extends BroadcastReceiver {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String msg = intent.getStringExtra(\"msg\");\n            ToastUtil.showToast(context, msg);\n\n            BroadCastHelper.notify(intent, context);\n        }\n    }\n\n    public static class CustomParcel implements Parcelable {\n        public CustomParcel() {\n        }\n\n        protected CustomParcel(Parcel in) {\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        public static final Creator<CustomParcel> CREATOR = new Creator<CustomParcel>() {\n            @Override\n            public CustomParcel createFromParcel(Parcel in) {\n                return new CustomParcel(in);\n            }\n\n            @Override\n            public CustomParcel[] newArray(int size) {\n                return new CustomParcel[size];\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/service/TestService.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.service;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.support.annotation.Nullable;\nimport android.support.v4.content.LocalBroadcastManager;\n\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\n\npublic class TestService extends Service {\n    private IBinder mBinder;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mBinder = new MyLocalServiceBinder();\n        ToastUtil.showToast(this, \"TestService onCreate\");\n        sendMsg(\"onCreate\");\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        sendMsg(\"onDestroy\");\n        ToastUtil.showToast(this, \"TestService onDestroy\");\n        mBinder = null;\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        ToastUtil.showToast(this, \"TestService onStartCommand\");\n        sendMsg(\"onStartCommand\");\n        return super.onStartCommand(intent, flags, startId);\n    }\n\n    @Nullable\n    @Override\n    public IBinder onBind(Intent intent) {\n        ToastUtil.showToast(this, \"TestService onBind\");\n        sendMsg(\"onBind\");\n        return mBinder;\n    }\n\n    @Override\n    public boolean onUnbind(Intent intent) {\n        ToastUtil.showToast(this, \"TestService unbindService\");\n        sendMsg(\"onUnbind\");\n        return super.onUnbind(intent);\n    }\n\n\n    public class MyLocalServiceBinder extends Binder {\n        public TestService getMyLocalService() {\n            return TestService.this;\n        }\n    }\n\n    public void test() {\n        sendMsg(\"callTest\");\n        ToastUtil.showToast(this, \"TestService\");\n    }\n\n\n    private void sendMsg(String msg) {\n        Intent intent = new Intent(TestStartServiceActivity.INTENT_ACTION);\n        intent.putExtra(\"result\", msg);\n        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/service/TestStartServiceActivity.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.service;\n\nimport android.app.Service;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport android.support.annotation.Nullable;\nimport android.support.v4.content.LocalBroadcastManager;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.ToastUtil;\nimport com.tencent.shadow.test.plugin.general_cases.lib.usecases.WithIdlingResourceActivity;\n\npublic class TestStartServiceActivity extends WithIdlingResourceActivity {\n\n    private Intent serviceIntent;\n\n    private TestService.MyLocalServiceBinder binder;\n\n    private TextView mTextView;\n\n    public final static String INTENT_ACTION = \"com.tencent.shadow.test.service\";\n\n    private Handler mHandler = new Handler();\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        serviceIntent = new Intent(this, TestService.class);\n        setContentView(R.layout.layout_service);\n        mTextView = findViewById(R.id.tv_msg);\n        LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, new IntentFilter(INTENT_ACTION));\n    }\n\n    public void start(View view) {\n        setIdle();\n        startService(serviceIntent);\n    }\n\n    public void startInNewThread(View view) {\n        setIdle();\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                startService(serviceIntent);\n            }\n        }).start();\n    }\n\n    public void bind(View view) {\n        setIdle();\n        bindService(serviceIntent, serviceConnection, Service.BIND_AUTO_CREATE);\n    }\n\n    private ServiceConnection serviceConnection = new ServiceConnection() {\n        @Override\n        public void onServiceConnected(ComponentName name, IBinder service) {\n            binder = (TestService.MyLocalServiceBinder) service;\n        }\n\n        @Override\n        public void onServiceDisconnected(ComponentName name) {\n            binder = null;\n        }\n    };\n\n    public void stop(View view) {\n        setIdle();\n        stopService(serviceIntent);\n    }\n\n    public void unbind(View view) {\n        setIdle();\n        unbindService(serviceConnection);\n    }\n\n    public void testBinder(View view) {\n        setIdle();\n        if (binder == null) {\n            ToastUtil.showToast(this, \"请先bindService\");\n        } else {\n            binder.getMyLocalService().test();\n        }\n    }\n\n    private void setIdle() {\n        mHandler.removeCallbacksAndMessages(null);\n        mIdlingResource.setIdleState(false);\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                mIdlingResource.setIdleState(true);\n            }\n        }, 2000);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);\n    }\n\n    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            mHandler.removeCallbacksAndMessages(null);\n            String text = intent.getStringExtra(\"result\");\n            String oldText = mTextView.getText().toString();\n            if (!TextUtils.isEmpty(oldText)) {\n                text = oldText + \"-\" + text;\n            }\n            mTextView.setText(text);\n            mIdlingResource.setIdleState(true);\n        }\n    };\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/view/TestNullRefInXmlActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.view;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.ViewGroup;\nimport android.widget.ListView;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestNullRefInXmlActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        ListView listView = (ListView) getLayoutInflater().inflate(R.layout.layout_test_null_ref, null);\n        int cacheColorHint = listView.getCacheColorHint();\n\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"cacheColorHint\",\n                        \"cacheColorHint\",\n                        Integer.toString(cacheColorHint, 16)\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/view/TestViewConstructorCache.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tencent Shadow available.\n * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n * this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *     https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage com.tencent.shadow.test.plugin.general_cases.lib.usecases.view;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n\nimport com.tencent.shadow.test.lib.custom_view.TestViewConstructorCacheView;\nimport com.tencent.shadow.test.plugin.general_cases.R;\n\nimport dalvik.system.PathClassLoader;\n\npublic class TestViewConstructorCache extends Activity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.layout_test_view_cons_cache);\n        TestViewConstructorCacheView testView = findViewById(R.id.testView);\n\n        PathClassLoader pathClassLoader = (PathClassLoader) getApplication().getBaseContext().getClass().getClassLoader();\n\n        boolean assertTrue;\n        try {\n            assertTrue = pathClassLoader.loadClass(TestViewConstructorCacheView.class.getName()) != testView.getClass();\n        } catch (ClassNotFoundException e) {\n            throw new RuntimeException(\"宿主中应该也有同名View\");\n        }\n\n        if (!assertTrue) {\n            throw new AssertionError(\"插件和宿主中不应该能加载出相同View名的同一个类\");\n        }\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/com/tencent/shadow/test/plugin/general_cases/lib/usecases/view/TestViewIdActivity.java",
    "content": "package com.tencent.shadow.test.plugin.general_cases.lib.usecases.view;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.tencent.shadow.test.plugin.general_cases.R;\nimport com.tencent.shadow.test.plugin.general_cases.lib.gallery.util.UiUtil;\n\npublic class TestViewIdActivity extends Activity {\n\n    private ViewGroup mItemViewGroup;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        View inflateView = getLayoutInflater().inflate(R.layout.layout_test_view_id, null);\n        int idInXml = inflateView.getId();\n        int idInJava = R.id.test_id;\n        int idInResources = getResources().getIdentifier(\"test_id\", \"id\", getPackageName());\n\n        mItemViewGroup = UiUtil.setActivityContentView(this);\n\n        boolean isSame = idInXml == idInJava && idInXml == idInResources;\n\n        addItem(\"idInXml\", idInXml);\n        addItem(\"idInJava\", idInJava);\n        addItem(\"idInResources\", idInResources);\n        mItemViewGroup.addView(\n                UiUtil.makeItem(\n                        this,\n                        \"isSame\",\n                        \"isSame\",\n                        Boolean.toString(isSame)\n                )\n        );\n    }\n\n    private void addItem(String key, int value) {\n        ViewGroup item = UiUtil.makeItem(\n                this,\n                key,\n                key,\n                Integer.toHexString(value)\n        );\n        mItemViewGroup.addView(item);\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/java/org/xmlpull/v1/XmlPullParser.java",
    "content": "package org.xmlpull.v1;\n\n// 这个类和系统类重名，在插件中不应该被加载到\npublic interface XmlPullParser {\n}\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/activity_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ListView\n        android:id=\"@+id/al_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/activity_test_file_provider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".usecases.provider.TestFileProviderActivity\">\n\n    <Button\n        android:id=\"@+id/go_take_photo\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"\n        android:text=\"去拍照\"\n        android:textSize=\"20dp\" />\n\n    <ImageView\n        android:id=\"@+id/photo\"\n        android:layout_width=\"180dp\"\n        android:layout_height=\"320dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"@android:color/darker_gray\"\n        android:scaleType=\"centerInside\" />\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/activity_test_re_create_by_system.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".usecases.activity.TestActivityReCreateBySystem\">\n\n    <TextView\n        android:id=\"@+id/url_tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"18dp\" />\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_activity_lifecycle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:tag=\"tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"Activity生命周期测试\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_common.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#ffffff\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_gravity=\"center\"\n        android:textColor=\"@android:color/black\"\n        android:text=\"Test\"\n        android:tag=\"text\" />\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:tag=\"button\"\n        android:textColor=\"@android:color/background_dark\"\n        android:textSize=\"22sp\"\n        android:onClick=\"doClick\"\n        android:text=\"click\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <FrameLayout\n        android:id=\"@+id/fragment_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_fragment_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/lli_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"48dp\"\n        android:textSize=\"18dp\"\n        android:gravity=\"center_vertical\"\n        android:padding=\"10dp\"\n        android:textColor=\"#cccccc\"\n        tools:text=\"Test\" />\n</LinearLayout>"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_orientation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"setOrientation\"\n        android:text=\"改变屏幕横竖屏状态\"\n        android:layout_gravity=\"top\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_packagemanager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"20dp\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getApplicationInfo\"\n        android:text=\"getApplicationInfo\"\n        android:layout_gravity=\"top\" />\n\n\n    <Button\n        android:id=\"@+id/button1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getActivityInfo\"\n        android:text=\"getActivityInfo\"\n        android:layout_gravity=\"top\" />\n\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"getPackageInfo\"\n        android:text=\"getPackageInfo\" />\n\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/white\"\n\n        />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_provider_db.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:paddingLeft=\"20dp\"\n    android:orientation=\"vertical\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:id=\"@+id/button\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"insert\"\n            android:text=\"插入数据\" />\n\n        <Button\n            android:id=\"@+id/button1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"query\"\n            android:text=\"读取数据\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"update\"\n            android:text=\"更新数据\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"20dp\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"delete\"\n            android:text=\"删除数据\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"bulkInsert\"\n            android:text=\"批量插入\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginBottom=\"8dp\"\n            android:layout_marginLeft=\"10dp\"\n            android:layout_marginTop=\"8dp\"\n            android:onClick=\"call\"\n            android:text=\"call\" />\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/white\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_receiver.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n\n    <Button\n        android:id=\"@+id/button_1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:tag=\"button_static\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"TestNormalBraodcast\"\n        android:text=\"测试静态广播发送\" />\n\n\n    <Button\n        android:id=\"@+id/button_2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:tag=\"button_dynamic\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"TestDynamicBraodcast\"\n        android:text=\"测试动态广播发送\" />\n\n    <Button\n        android:id=\"@+id/button_3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:tag=\"button_unRegister_dynamic\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"TestUnregisterDynamicBraodcast\"\n        android:text=\"测试动态广播注销\" />\n\n\n    <TextView\n        android:id=\"@+id/content\"\n        android:layout_marginTop=\"20dp\"\n        android:tag=\"text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_recreate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <Button\n        android:id=\"@+id/button\"\n        android:tag=\"button\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:onClick=\"reCreate\"\n        android:text=\"reCreate\"\n        android:layout_gravity=\"top\" />\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:tag=\"tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"Result:\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_result.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:gravity=\"center\">\n\n    <TextView\n        android:id=\"@+id/result\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"18sp\"\n        android:textColor=\"@android:color/black\"\n        android:padding=\"20dp\"\n        android:layout_gravity=\"center\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_service.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_marginTop=\"16dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:tag=\"start\"\n            android:onClick=\"start\"\n            android:text=\"startService\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:tag=\"startInNewThread\"\n            android:onClick=\"startInNewThread\"\n            android:text=\"startInNewThread\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:tag=\"bind\"\n            android:onClick=\"bind\"\n            android:text=\"bindService\" />\n\n    </LinearLayout>\n\n\n    <LinearLayout\n        android:layout_marginTop=\"16dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:onClick=\"stop\"\n            android:tag=\"stop\"\n            android:text=\"stopService\" />\n\n        <Button\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"8dp\"\n            android:onClick=\"unbind\"\n            android:tag=\"unbind\"\n            android:text=\"unbindService\" />\n\n\n    </LinearLayout>\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"top\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"8dp\"\n        android:onClick=\"testBinder\"\n        android:tag=\"testBinder\"\n        android:text=\"testBinder调用\" />\n\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"30dp\"\n        android:tag=\"text\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n\n</LinearLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_softmode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:layout_gravity=\"center\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"18sp\"\n            android:textColor=\"@android:color/black\"\n            android:padding=\"20dp\"\n            android:text=\"AndroidManifest中设置了windowSoftInputMode属性为stateVisible,输入法应该自动弹出\" />\n\n\n        <TextView\n            android:id=\"@+id/result\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"18sp\"\n            android:textColor=\"@android:color/black\"\n            android:padding=\"20dp\" />\n\n    </LinearLayout>\n\n\n    <EditText\n        android:id=\"@+id/edit_view\"\n        android:layout_gravity=\"bottom\"\n        android:hint=\"请输入\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"40dp\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_test_null_ref.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ListView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_height=\"match_parent\"\n    android:layout_width=\"match_parent\"\n    android:cacheColorHint=\"@null\" />\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_test_view_cons_cache.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_gravity=\"center\"\n        android:text=\"没有Crash就是正常的\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"20sp\" />\n\n    <com.tencent.shadow.test.lib.custom_view.TestViewConstructorCacheView\n        android:id=\"@+id/testView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_test_view_id.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/test_id\"\n    android:layout_height=\"match_parent\"\n    android:layout_width=\"match_parent\"\n    android:orientation=\"vertical\">\n\n</LinearLayout>"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_xml_add_base_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <fragment\n        android:name=\"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.SubTestBaseFragment\"\n        android:tag=\"TestFragmentTag\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_xml_add_normal_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <fragment\n        android:name=\"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.TestNormalFragment\"\n        android:tag=\"TestFragmentTag\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/layout/layout_xml_add_sub_fragment_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"horizontal\">\n\n\n    <fragment\n        android:name=\"com.tencent.shadow.test.plugin.general_cases.lib.usecases.fragment.TestSubFragment\"\n        android:tag=\"TestFragmentTag\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <!-- Simple strings. -->\n    <string name=\"app_name\">Shadow主测试用例集合</string>\n</resources>\n\n"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n\n    <style name=\"TestPluginTheme\" parent=\"@android:style/Theme.WithActionBar\">\n        <!--插件设置的android:windowIsTranslucent不能生效,宿主中注册的壳子Activity必须设置windowIsTranslucent==true-->\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:background\">#64f56701</item>\n    </style>\n</resources>"
  },
  {
    "path": "projects/test/plugin/general-cases/test-plugin-general-cases/src/main/res/xml/filepaths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Tencent is pleased to support the open source community by making Tencent Shadow available.\n  ~ Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.\n  ~\n  ~ Licensed under the BSD 3-Clause License (the \"License\"); you may not use\n  ~ this file except in compliance with the License. You may obtain a copy of\n  ~ the License at\n  ~\n  ~     https://opensource.org/licenses/BSD-3-Clause\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->\n\n<resources>\n    <root-path\n        name=\"name\"\n        path=\"\" />\n\n    <file-path\n        name=\"files\"\n        path=\"/\" />\n\n    <cache-path\n        name=\"cache\"\n        path=\"/\" />\n\n    <external-files-path\n        name=\"external\"\n        path=\"/\" />\n</resources>"
  },
  {
    "path": "projects/test/plugin/particular-cases/plugin-service-for-host/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "projects/test/plugin/particular-cases/plugin-service-for-host/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.tencent.shadow.plugin'\n\nandroid {\n    compileSdkVersion project.COMPILE_SDK_VERSION\n\n    defaultConfig {\n        applicationId 'com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host'\n        minSdkVersion project.MIN_SDK_VERSION\n        targetSdkVersion project.TARGET_SDK_VERSION\n        versionCode project.VERSION_CODE\n        versionName project.VERSION_NAME\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            signingConfig signingConfigs.create(\"release\")\n            signingConfig.initWith(buildTypes.debug.signingConfig)\n        }\n    }\n\n    // 将插件applicationId设置为和宿主相同\n    productFlavors {\n        plugin {\n            applicationId project.TEST_HOST_APP_APPLICATION_ID\n        }\n    }\n\n    // 将插件的资源ID分区改为和宿主0x7F不同的值\n    aaptOptions {\n        additionalParameters \"--package-id\", \"0x7E\", \"--allow-reserved-package-id\"\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    //Shadow Transform后业务代码会有一部分实际引用runtime中的类\n    //如果不以compileOnly方式依赖，会导致其他Transform或者Proguard找不到这些类\n    pluginCompileOnly 'com.tencent.shadow.core:runtime'\n}\n\nbuildscript {\n    repositories {\n        if (!System.getenv().containsKey(\"DISABLE_TENCENT_MAVEN_MIRROR\")) {\n            maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' }\n        } else {\n            google()\n            mavenCentral()\n        }\n    }\n\n    dependencies {\n        classpath 'com.tencent.shadow.core:runtime'\n        classpath 'com.tencent.shadow.core:activity-container'\n        classpath 'com.tencent.shadow.core:gradle-plugin'\n        classpath \"org.javassist:javassist:$javassist_version\"\n    }\n}\n\n"
  },
  {
    "path": "projects/test/plugin/particular-cases/plugin-service-for-host/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host\">\n\n    <application tools:ignore=\"AllowBackup,MissingApplicationIcon\">\n        <service android:name=\".SystemExitService\" />\n        <service android:name=\".SystemExitIntentService\" />\n    </application>\n</manifest>"
  },
  {
    "path": "projects/test/plugin/particular-cases/plugin-service-for-host/src/main/java/com/tencent/shadow/test/plugin/particular_cases/plugin_service_for_host/SystemExitIntentService.java",
    "content": "package com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host;\n\nimport android.app.IntentService;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\npublic class SystemExitIntentService extends IntentService {\n\n    public SystemExitIntentService() {\n        super(SystemExitIntentService.class.getSimpleName());\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return new Binder() {\n            @Override\n            protected boolean onTransact(int code,\n                                         Parcel data,\n                                         Parcel reply,\n                                         int flags) throws RemoteException {\n                //随便发什么来都退出进程以便触发onServiceDisconnected\n                System.exit(0);\n                return super.onTransact(code, data, reply, flags);\n            }\n        };\n    }\n\n    @Override\n    protected void onHandleIntent(Intent intent) {\n\n    }\n}\n"
  },
  {
    "path": "projects/test/plugin/particular-cases/plugin-service-for-host/src/main/java/com/tencent/shadow/test/plugin/particular_cases/plugin_service_for_host/SystemExitService.java",
    "content": "package com.tencent.shadow.test.plugin.particular_cases.plugin_service_for_host;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileWriter;\n\n@SuppressWarnings(\"NullableProblems\")\npublic class SystemExitService extends Service {\n    @Override\n    public IBinder onBind(Intent intent) {\n        return new Binder() {\n            @Override\n            protected boolean onTransact(int code,\n                                         Parcel data,\n                                         Parcel reply,\n                                         int flags) throws RemoteException {\n                //随便发什么来都退出进程以便触发onServiceDisconnected\n                System.exit(0);\n                return super.onTransact(code, data, reply, flags);\n            }\n        };\n    }\n\n    @Override\n    public boolean onUnbind(Intent intent) {\n        long magic_number = intent.getLongExtra(\"magic_number\", 0L);\n        File outputFile = new File(getFilesDir(), \"SystemExitService.onUnbind\");\n        try (BufferedWriter bufferedWriter\n                     = new BufferedWriter(new FileWriter(outputFile, false))) {\n            bufferedWriter.append(Long.toString(magic_number));\n            bufferedWriter.flush();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n\n        return super.onUnbind(intent);\n    }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "import java.nio.file.Files\n\nrootProject.name = 'shadow'\n\ncopyLocalPropertiesForIncludeBuilds()\nincludeBuild 'projects/sdk/coding'\nincludeBuild 'projects/sdk/core'\nincludeBuild 'projects/sdk/dynamic'\n\nincludeTest()\nincludeSample()\nincludeSampleDynamicApk()\n\ndef includeTest() {\n    include 'test-dynamic-host',\n            'test-dynamic-manager',\n            'test-dynamic-loader',\n            'test-dynamic-runtime',\n            'common-jar-settings-test',\n            'constant',\n            'custom-view',\n            'plugin-use-host-code-lib',\n            'test-manager',\n            'general-cases-lib',\n            'test-plugin-general-cases',\n            'general-cases-app',\n            'androidx-cases-lib',\n            'androidx-cases-app',\n            'test-plugin-androidx-cases',\n            'plugin-service-for-host',\n            'test-none-dynamic-host'\n    project(':test-dynamic-host').projectDir = file('projects/test/dynamic/host/test-dynamic-host')\n    project(':test-dynamic-manager').projectDir = file('projects/test/dynamic/manager/test-dynamic-manager')\n    project(':test-dynamic-loader').projectDir = file('projects/test/dynamic/plugin/test-dynamic-loader')\n    project(':test-dynamic-runtime').projectDir = file('projects/test/dynamic/plugin/test-dynamic-runtime')\n    project(':common-jar-settings-test').projectDir = file('projects/test/common-jar-settings-test')\n    project(':constant').projectDir = file('projects/test/lib/constant')\n    project(':custom-view').projectDir = file('projects/test/lib/custom-view')\n    project(':plugin-use-host-code-lib').projectDir = file('projects/test/lib/plugin-use-host-code-lib')\n    project(':test-manager').projectDir = file('projects/test/lib/test-manager')\n    project(':general-cases-lib').projectDir = file('projects/test/plugin/general-cases/general-cases-lib')\n    project(':androidx-cases-lib').projectDir = file('projects/test/plugin/androidx-cases/androidx-cases-lib')\n    project(':androidx-cases-app').projectDir = file('projects/test/plugin/androidx-cases/androidx-cases-app')\n    project(':test-plugin-androidx-cases').projectDir = file('projects/test/plugin/androidx-cases/test-plugin-androidx-cases')\n    project(':test-plugin-general-cases').projectDir = file('projects/test/plugin/general-cases/test-plugin-general-cases')\n    project(':general-cases-app').projectDir = file('projects/test/plugin/general-cases/general-cases-app')\n    project(':plugin-service-for-host').projectDir = file('projects/test/plugin/particular-cases/plugin-service-for-host')\n    project(':test-none-dynamic-host').projectDir = file('projects/test/none-dynamic/host/test-none-dynamic-host')\n}\n\ndef includeSample() {\n    include 'sample-constant',\n            'sample-host',\n            'sample-host-lib',\n            'sample-manager',\n            'sample-loader',\n            'sample-runtime',\n            'pinnedheaderexpandablelistview',\n            'slidingmenu',\n            'sample-base-lib',\n            'sample-base',\n            'sample-app'\n    project(':sample-constant').projectDir = file('projects/sample/source/sample-constant')\n    project(':sample-host').projectDir = file('projects/sample/source/sample-host')\n    project(':sample-host-lib').projectDir = file('projects/sample/source/sample-host-lib')\n    project(':sample-manager').projectDir = file('projects/sample/source/sample-manager')\n    project(':sample-loader').projectDir = file('projects/sample/source/sample-plugin/sample-loader')\n    project(':sample-runtime').projectDir = file('projects/sample/source/sample-plugin/sample-runtime')\n    project(':pinnedheaderexpandablelistview').projectDir = file('projects/sample/source/sample-plugin/third-party/pinnedheaderexpandablelistview')\n    project(':slidingmenu').projectDir = file('projects/sample/source/sample-plugin/third-party/slidingmenu')\n    project(':sample-base-lib').projectDir = file('projects/sample/source/sample-plugin/sample-base-lib')\n    project(':sample-base').projectDir = file('projects/sample/source/sample-plugin/sample-base')\n    project(':sample-app').projectDir = file('projects/sample/source/sample-plugin/sample-app')\n}\n\ndef includeSampleDynamicApk() {\n    include 'sample-hello-api',\n            'sample-hello-api-holder',\n            'sample-hello-apk',\n            'sample-hello-host'\n    project(':sample-hello-api').projectDir = file('projects/sample/dynamic-apk/sample-hello-api')\n    project(':sample-hello-api-holder').projectDir = file('projects/sample/dynamic-apk/sample-hello-api-holder')\n    project(':sample-hello-apk').projectDir = file('projects/sample/dynamic-apk/sample-hello-apk')\n    project(':sample-hello-host').projectDir = file('projects/sample/dynamic-apk/sample-hello-host')\n}\n\n/**\n * Android Studio当前不会为IncludeBuild创建包含sdk.dir的local.properties\n * 使得没有ANDROID_HOME或等效环境变量时仅依赖根目录的local.properties无法编译IncludeBuild。\n * 为了使此含有IncludeBuild的项目和其他不含有IncludeBuild的普通Android工程一样可以在\n * 只有根目录的local.properties情况下正常编译，用此任务复制local.properties。\n */\ndef copyLocalPropertiesForIncludeBuilds() {\n    def rootFile = file('local.properties')\n    if (rootFile.exists()) {\n        ['coding', 'core', 'dynamic'].forEach {\n            def includeBuildFile = file(\"projects/sdk/${it}/local.properties\")\n            if (!includeBuildFile.exists()) {\n                Files.copy(rootFile.toPath(), includeBuildFile.toPath())\n            }\n        }\n    }\n}\n"
  }
]