Repository: Tencent/tinker Branch: dev Commit: d40dfcbd24e6 Files: 427 Total size: 2.5 MB Directory structure: gitextract_2owebcte/ ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── checkstyle.xml ├── findbugs-exclude.xml ├── gradle/ │ ├── PublishArtifact.gradle │ ├── WeChatPublish.gradle │ ├── check.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── pmd-ruleset.xml ├── settings.gradle ├── suppressions.xml ├── third-party/ │ ├── aosp-dexutils/ │ │ ├── .gitignore │ │ ├── NOTICE │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── android/ │ │ ├── dex/ │ │ │ ├── Annotation.java │ │ │ ├── AnnotationSet.java │ │ │ ├── AnnotationSetRefList.java │ │ │ ├── AnnotationsDirectory.java │ │ │ ├── CallSiteId.java │ │ │ ├── ClassData.java │ │ │ ├── ClassDef.java │ │ │ ├── Code.java │ │ │ ├── DebugInfoItem.java │ │ │ ├── Dex.java │ │ │ ├── DexException.java │ │ │ ├── DexFormat.java │ │ │ ├── EncodedValue.java │ │ │ ├── EncodedValueCodec.java │ │ │ ├── EncodedValueReader.java │ │ │ ├── FieldId.java │ │ │ ├── Leb128.java │ │ │ ├── MethodHandle.java │ │ │ ├── MethodId.java │ │ │ ├── Mutf8.java │ │ │ ├── ProtoId.java │ │ │ ├── SizeOf.java │ │ │ ├── StringData.java │ │ │ ├── TableOfContents.java │ │ │ ├── TypeList.java │ │ │ ├── io/ │ │ │ │ └── DexDataBuffer.java │ │ │ └── util/ │ │ │ ├── ByteInput.java │ │ │ ├── ByteOutput.java │ │ │ ├── CompareUtils.java │ │ │ ├── FileUtils.java │ │ │ └── HashCodeHelper.java │ │ ├── dx/ │ │ │ ├── instruction/ │ │ │ │ ├── CodeCursor.java │ │ │ │ ├── InstructionCodec.java │ │ │ │ ├── InstructionComparator.java │ │ │ │ ├── InstructionPromoter.java │ │ │ │ ├── InstructionReader.java │ │ │ │ ├── InstructionVisitor.java │ │ │ │ ├── InstructionWriter.java │ │ │ │ ├── Opcodes.java │ │ │ │ ├── ShortArrayCodeInput.java │ │ │ │ └── ShortArrayCodeOutput.java │ │ │ └── util/ │ │ │ └── Hex.java │ │ └── utils/ │ │ ├── SparseBoolArray.java │ │ └── SparseIntArray.java │ ├── bsdiff-util/ │ │ ├── .gitignore │ │ ├── LICENSE.txt │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── bsdiff/ │ │ ├── BSDiff.java │ │ ├── BSPatch.java │ │ └── BSUtil.java │ └── tinker-ziputils/ │ ├── .gitignore │ ├── NOTICE.txt │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── tencent/ │ └── tinker/ │ └── ziputils/ │ └── ziputil/ │ ├── AlignedZipOutputStream.java │ ├── Arrays.java │ ├── BufferIterator.java │ ├── HeapBufferIterator.java │ ├── Memory.java │ ├── SizeOf.java │ ├── StandardCharsets.java │ ├── Streams.java │ ├── TinkerZipEntry.java │ ├── TinkerZipFile.java │ ├── TinkerZipOutputStream.java │ ├── TinkerZipUtil.java │ └── ZipConstants.java ├── tinker-android/ │ ├── consumer-proguard.txt │ ├── tinker-android-anno/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ ├── main/ │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── javax.annotation.processing.Processor │ │ │ └── TinkerAnnoApplication.tmpl │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── anno/ │ │ └── test/ │ │ └── TestLifeCycle.java │ ├── tinker-android-anno-support/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── anno/ │ │ ├── AnnotationProcessor.java │ │ ├── DefaultLifeCycle.java │ │ └── Keep.java │ ├── tinker-android-lib/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── lib/ │ │ │ └── patch/ │ │ │ └── ApplicationTest.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── aidl/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── tinker/ │ │ │ │ └── lib/ │ │ │ │ └── IForeService.aidl │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ ├── entry/ │ │ │ │ ├── ApplicationLifeCycle.java │ │ │ │ ├── ApplicationLike.java │ │ │ │ ├── DefaultApplicationLike.java │ │ │ │ └── TinkerApplicationInlineFence.java │ │ │ └── lib/ │ │ │ ├── filepatch/ │ │ │ │ ├── AbstractFilePatch.java │ │ │ │ ├── BsFilePatch.java │ │ │ │ └── FilePatchFactory.java │ │ │ ├── library/ │ │ │ │ └── TinkerLoadLibrary.java │ │ │ ├── listener/ │ │ │ │ ├── DefaultPatchListener.java │ │ │ │ └── PatchListener.java │ │ │ ├── patch/ │ │ │ │ ├── AbstractPatch.java │ │ │ │ ├── ArkHotDiffPatchInternal.java │ │ │ │ ├── BasePatchInternal.java │ │ │ │ ├── DexDiffPatchInternal.java │ │ │ │ ├── ResDiffPatchInternal.java │ │ │ │ ├── SoDiffPatchInternal.java │ │ │ │ └── UpgradePatch.java │ │ │ ├── reporter/ │ │ │ │ ├── DefaultLoadReporter.java │ │ │ │ ├── DefaultPatchReporter.java │ │ │ │ ├── LoadReporter.java │ │ │ │ └── PatchReporter.java │ │ │ ├── service/ │ │ │ │ ├── AbstractResultService.java │ │ │ │ ├── DefaultTinkerResultService.java │ │ │ │ ├── PatchResult.java │ │ │ │ ├── TinkerPatchForeService.java │ │ │ │ └── TinkerPatchService.java │ │ │ ├── tinker/ │ │ │ │ ├── Tinker.java │ │ │ │ ├── TinkerApplicationHelper.java │ │ │ │ ├── TinkerInstaller.java │ │ │ │ └── TinkerLoadResult.java │ │ │ └── util/ │ │ │ ├── TinkerLog.java │ │ │ ├── TinkerServiceInternals.java │ │ │ └── UpgradePatchRetry.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── recover/ │ │ └── ExampleUnitTest.java │ ├── tinker-android-lib-no-op/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── lib/ │ │ │ └── patch/ │ │ │ └── ApplicationTest.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── lib/ │ │ │ ├── library/ │ │ │ │ └── TinkerLoadLibrary.java │ │ │ ├── listener/ │ │ │ │ ├── DefaultPatchListener.java │ │ │ │ └── PatchListener.java │ │ │ ├── patch/ │ │ │ │ ├── AbstractPatch.java │ │ │ │ └── UpgradePatch.java │ │ │ ├── reporter/ │ │ │ │ ├── DefaultLoadReporter.java │ │ │ │ ├── DefaultPatchReporter.java │ │ │ │ ├── LoadReporter.java │ │ │ │ └── PatchReporter.java │ │ │ ├── service/ │ │ │ │ ├── AbstractResultService.java │ │ │ │ ├── DefaultTinkerResultService.java │ │ │ │ └── PatchResult.java │ │ │ ├── tinker/ │ │ │ │ ├── Tinker.java │ │ │ │ ├── TinkerApplicationHelper.java │ │ │ │ ├── TinkerInstaller.java │ │ │ │ └── TinkerLoadResult.java │ │ │ └── util/ │ │ │ ├── TinkerLog.java │ │ │ ├── TinkerServiceInternals.java │ │ │ └── UpgradePatchRetry.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── recover/ │ │ └── ExampleUnitTest.java │ ├── tinker-android-loader/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules.pro │ │ ├── src/ │ │ │ ├── androidTest/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── tinker/ │ │ │ │ └── loader/ │ │ │ │ └── ApplicationTest.java │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── tinker/ │ │ │ │ └── loader/ │ │ │ │ ├── AbstractTinkerLoader.java │ │ │ │ ├── AppInfoChangedBlocker.java │ │ │ │ ├── NewClassLoaderInjector.java │ │ │ │ ├── SystemClassLoaderAdder.java │ │ │ │ ├── TinkerArkHotLoader.java │ │ │ │ ├── TinkerClassLoader.java │ │ │ │ ├── TinkerDexLoader.java │ │ │ │ ├── TinkerDexOptimizer.java │ │ │ │ ├── TinkerLoader.java │ │ │ │ ├── TinkerResourceLoader.java │ │ │ │ ├── TinkerResourcePatcher.java │ │ │ │ ├── TinkerResourcesKey.java │ │ │ │ ├── TinkerRuntimeException.java │ │ │ │ ├── TinkerSoLoader.java │ │ │ │ ├── TinkerTestDexLoad.java │ │ │ │ ├── TinkerUncaughtHandler.java │ │ │ │ ├── app/ │ │ │ │ │ ├── TinkerApplication.java │ │ │ │ │ └── TinkerInlineFenceAction.java │ │ │ │ ├── hotplug/ │ │ │ │ │ ├── ActivityStubManager.java │ │ │ │ │ ├── ActivityStubs.java │ │ │ │ │ ├── ComponentHotplug.java │ │ │ │ │ ├── EnvConsts.java │ │ │ │ │ ├── IncrementComponentManager.java │ │ │ │ │ ├── UnsupportedEnvironmentException.java │ │ │ │ │ ├── handler/ │ │ │ │ │ │ ├── AMSInterceptHandler.java │ │ │ │ │ │ ├── MHMessageHandler.java │ │ │ │ │ │ └── PMSInterceptHandler.java │ │ │ │ │ └── interceptor/ │ │ │ │ │ ├── HandlerMessageInterceptor.java │ │ │ │ │ ├── InterceptFailedException.java │ │ │ │ │ ├── Interceptor.java │ │ │ │ │ ├── ServiceBinderInterceptor.java │ │ │ │ │ └── TinkerHackInstrumentation.java │ │ │ │ └── shareutil/ │ │ │ │ ├── Guard.java │ │ │ │ ├── ShareArkHotDiffPatchInfo.java │ │ │ │ ├── ShareBsDiffPatchInfo.java │ │ │ │ ├── ShareConstants.java │ │ │ │ ├── ShareDexDiffPatchInfo.java │ │ │ │ ├── ShareElfFile.java │ │ │ │ ├── ShareFileLockHelper.java │ │ │ │ ├── ShareIntentUtil.java │ │ │ │ ├── ShareOatUtil.java │ │ │ │ ├── SharePatchFileUtil.java │ │ │ │ ├── SharePatchInfo.java │ │ │ │ ├── ShareReflectUtil.java │ │ │ │ ├── ShareResPatchInfo.java │ │ │ │ ├── ShareSecurityCheck.java │ │ │ │ ├── ShareTinkerInternals.java │ │ │ │ ├── ShareTinkerLog.java │ │ │ │ └── TinkerLogInlineFence.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── loader/ │ │ │ ├── ExampleUnitTest.java │ │ │ └── shareutil/ │ │ │ └── GuardTest.java │ │ └── stubs/ │ │ └── sysapi-access-stub.jar │ └── tinker-android-loader-no-op/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ └── loader/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── tinker/ │ │ ├── entry/ │ │ │ ├── ApplicationLifeCycle.java │ │ │ ├── ApplicationLike.java │ │ │ └── DefaultApplicationLike.java │ │ └── loader/ │ │ ├── TinkerRuntimeException.java │ │ ├── app/ │ │ │ └── TinkerApplication.java │ │ └── shareutil/ │ │ ├── ShareArkHotDiffPatchInfo.java │ │ ├── ShareBsDiffPatchInfo.java │ │ ├── ShareConstants.java │ │ ├── ShareDexDiffPatchInfo.java │ │ ├── ShareElfFile.java │ │ ├── ShareFileLockHelper.java │ │ ├── ShareIntentUtil.java │ │ ├── ShareOatUtil.java │ │ ├── SharePatchFileUtil.java │ │ ├── SharePatchInfo.java │ │ ├── ShareReflectUtil.java │ │ ├── ShareResPatchInfo.java │ │ ├── ShareSecurityCheck.java │ │ ├── ShareTinkerInternals.java │ │ ├── ShareTinkerLog.java │ │ └── TinkerLogInlineFence.java │ └── test/ │ └── java/ │ └── com/ │ └── tencent/ │ └── tinker/ │ └── loader/ │ └── ExampleUnitTest.java ├── tinker-build/ │ ├── tinker-patch-cli/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── patch/ │ │ │ ├── CliMain.java │ │ │ └── Test.java │ │ ├── tool_maple/ │ │ │ ├── DexCmp.jar │ │ │ └── build_patch_dexdiff.sh │ │ └── tool_output/ │ │ ├── merge_mapping.py │ │ ├── proguard_warning.py │ │ ├── release.keystore │ │ ├── tinker_config.xml │ │ ├── tinker_multidexkeep.pro │ │ └── tinker_proguard.pro │ ├── tinker-patch-gradle-plugin/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ └── main/ │ │ ├── groovy/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── build/ │ │ │ └── gradle/ │ │ │ ├── Compatibilities.groovy │ │ │ ├── TinkerPatchPlugin.groovy │ │ │ ├── common/ │ │ │ │ └── TinkerBuildPath.groovy │ │ │ ├── extension/ │ │ │ │ ├── TinkerArkHotExtension.groovy │ │ │ │ ├── TinkerBuildConfigExtension.groovy │ │ │ │ ├── TinkerDexExtension.groovy │ │ │ │ ├── TinkerLibExtension.groovy │ │ │ │ ├── TinkerPackageConfigExtension.groovy │ │ │ │ ├── TinkerPatchExtension.groovy │ │ │ │ ├── TinkerResourceExtension.groovy │ │ │ │ └── TinkerSevenZipExtension.groovy │ │ │ ├── task/ │ │ │ │ ├── TinkerManifestAction.groovy │ │ │ │ ├── TinkerMultidexConfigTask.groovy │ │ │ │ ├── TinkerPatchSchemaTask.groovy │ │ │ │ ├── TinkerProguardConfigAction.groovy │ │ │ │ └── TinkerResourceIdTask.groovy │ │ │ └── transform/ │ │ │ └── ImmutableDexTransform.groovy │ │ └── resources/ │ │ └── META-INF/ │ │ └── gradle-plugins/ │ │ └── com.tencent.tinker.patch.properties │ └── tinker-patch-lib/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── java/ │ │ ├── com/ │ │ │ └── tencent/ │ │ │ └── tinker/ │ │ │ └── build/ │ │ │ ├── aapt/ │ │ │ │ ├── AaptResourceCollector.java │ │ │ │ ├── AaptUtil.java │ │ │ │ ├── Constant.java │ │ │ │ ├── FakeRDotTxtEntry.java │ │ │ │ ├── FileCopyException.java │ │ │ │ ├── FileUtil.java │ │ │ │ ├── Generator.java │ │ │ │ ├── JavaXmlUtil.java │ │ │ │ ├── ObjectUtil.java │ │ │ │ ├── PatchUtil.java │ │ │ │ ├── RDotTxtEntry.java │ │ │ │ ├── ResourceDirectory.java │ │ │ │ ├── ResourceEntry.java │ │ │ │ └── StringUtil.java │ │ │ ├── apkparser/ │ │ │ │ └── AndroidParser.java │ │ │ ├── builder/ │ │ │ │ └── PatchBuilder.java │ │ │ ├── decoder/ │ │ │ │ ├── ApkDecoder.java │ │ │ │ ├── ArkHotDecoder.java │ │ │ │ ├── BaseDecoder.java │ │ │ │ ├── DexDiffDecoder.java │ │ │ │ ├── ManifestDecoder.java │ │ │ │ ├── ResDiffDecoder.java │ │ │ │ ├── SoDiffDecoder.java │ │ │ │ └── UniqueDexDiffDecoder.java │ │ │ ├── dexpatcher/ │ │ │ │ ├── DexPatchGenerator.java │ │ │ │ ├── algorithms/ │ │ │ │ │ └── diff/ │ │ │ │ │ ├── AnnotationSectionDiffAlgorithm.java │ │ │ │ │ ├── AnnotationSetRefListSectionDiffAlgorithm.java │ │ │ │ │ ├── AnnotationSetSectionDiffAlgorithm.java │ │ │ │ │ ├── AnnotationsDirectorySectionDiffAlgorithm.java │ │ │ │ │ ├── CallSiteIdSectionDiffAlgorithm.java │ │ │ │ │ ├── ClassDataSectionDiffAlgorithm.java │ │ │ │ │ ├── ClassDefSectionDiffAlgorithm.java │ │ │ │ │ ├── CodeSectionDiffAlgorithm.java │ │ │ │ │ ├── DebugInfoItemSectionDiffAlgorithm.java │ │ │ │ │ ├── DexSectionDiffAlgorithm.java │ │ │ │ │ ├── FieldIdSectionDiffAlgorithm.java │ │ │ │ │ ├── MethodHandleSectionDiffAlgorithm.java │ │ │ │ │ ├── MethodIdSectionDiffAlgorithm.java │ │ │ │ │ ├── ProtoIdSectionDiffAlgorithm.java │ │ │ │ │ ├── StaticValueSectionDiffAlgorithm.java │ │ │ │ │ ├── StringDataSectionDiffAlgorithm.java │ │ │ │ │ ├── TypeIdSectionDiffAlgorithm.java │ │ │ │ │ └── TypeListSectionDiffAlgorithm.java │ │ │ │ └── util/ │ │ │ │ ├── ChangedClassesDexClassInfoCollector.java │ │ │ │ └── PatternUtils.java │ │ │ ├── immutable/ │ │ │ │ ├── ClassSimDef.java │ │ │ │ └── DexRefData.java │ │ │ ├── info/ │ │ │ │ ├── InfoWriter.java │ │ │ │ ├── PatchInfo.java │ │ │ │ └── PatchInfoGen.java │ │ │ ├── patch/ │ │ │ │ ├── Configuration.java │ │ │ │ ├── InputParam.java │ │ │ │ └── Runner.java │ │ │ └── util/ │ │ │ ├── CustomDiff.java │ │ │ ├── DexClassesComparator.java │ │ │ ├── DiffFactory.java │ │ │ ├── ExcludedClassModifiedChecker.java │ │ │ ├── FileOperation.java │ │ │ ├── Logger.java │ │ │ ├── MD5.java │ │ │ ├── TinkerPatchException.java │ │ │ ├── TypedValue.java │ │ │ └── Utils.java │ │ └── org/ │ │ └── jf/ │ │ └── dexlib2/ │ │ └── builder/ │ │ └── BuilderMutableMethodImplementation.java │ └── resources/ │ ├── only_use_to_test_tinker_resource.txt │ └── test.dex ├── tinker-commons/ │ ├── .gitignore │ ├── NOTICE.txt │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── tencent/ │ └── tinker/ │ └── commons/ │ ├── dexpatcher/ │ │ ├── DexPatchApplier.java │ │ ├── DexPatcherLogger.java │ │ ├── algorithms/ │ │ │ └── patch/ │ │ │ ├── AnnotationSectionPatchAlgorithm.java │ │ │ ├── AnnotationSetRefListSectionPatchAlgorithm.java │ │ │ ├── AnnotationSetSectionPatchAlgorithm.java │ │ │ ├── AnnotationsDirectorySectionPatchAlgorithm.java │ │ │ ├── CallSiteIdSectionPatchAlgorithm.java │ │ │ ├── ClassDataSectionPatchAlgorithm.java │ │ │ ├── ClassDefSectionPatchAlgorithm.java │ │ │ ├── CodeSectionPatchAlgorithm.java │ │ │ ├── DebugInfoItemSectionPatchAlgorithm.java │ │ │ ├── DexSectionPatchAlgorithm.java │ │ │ ├── FieldIdSectionPatchAlgorithm.java │ │ │ ├── MethodHandleSectionPatchAlgorithm.java │ │ │ ├── MethodIdSectionPatchAlgorithm.java │ │ │ ├── ProtoIdSectionPatchAlgorithm.java │ │ │ ├── StaticValueSectionPatchAlgorithm.java │ │ │ ├── StringDataSectionPatchAlgorithm.java │ │ │ ├── TypeIdSectionPatchAlgorithm.java │ │ │ └── TypeListSectionPatchAlgorithm.java │ │ ├── struct/ │ │ │ ├── DexPatchFile.java │ │ │ └── PatchOperation.java │ │ └── util/ │ │ ├── AbstractIndexMap.java │ │ ├── InstructionTransformer.java │ │ └── SparseIndexMap.java │ └── util/ │ ├── DigestUtil.java │ └── IOHelper.java └── tinker-sample-android/ ├── .gitignore ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── keystore/ │ │ ├── debug.keystore │ │ └── release.keystore │ ├── proguard-rules.pro │ ├── src/ │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── tinker/ │ │ │ │ └── sample/ │ │ │ │ └── android/ │ │ │ │ ├── Log/ │ │ │ │ │ └── MyLogImp.java │ │ │ │ ├── app/ │ │ │ │ │ ├── BaseBuildInfo.java │ │ │ │ │ ├── BuildInfo.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── SampleApplicationLike.java │ │ │ │ ├── crash/ │ │ │ │ │ └── SampleUncaughtExceptionHandler.java │ │ │ │ ├── reporter/ │ │ │ │ │ ├── SampleLoadReporter.java │ │ │ │ │ ├── SamplePatchListener.java │ │ │ │ │ ├── SamplePatchReporter.java │ │ │ │ │ └── SampleTinkerReport.java │ │ │ │ ├── service/ │ │ │ │ │ └── SampleResultService.java │ │ │ │ └── util/ │ │ │ │ ├── SampleApplicationContext.java │ │ │ │ ├── TinkerManager.java │ │ │ │ └── Utils.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ └── activity_main.xml │ │ │ ├── values/ │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── values-w820dp/ │ │ │ └── dimens.xml │ │ └── test/ │ │ └── java/ │ │ └── tinker/ │ │ └── sample/ │ │ └── android/ │ │ └── ExampleUnitTest.java │ └── tinker_multidexkeep.pro ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── updateTinkerLib.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ## Issue/提问须知 **在提交issue之前,我们应该先查询是否已经有相关的issue以及[常见问题](https://github.com/Tencent/tinker/wiki/Tinker-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)。提交issue时,我们需要写明issue的原因,以及编译或运行过程的日志(加载进程以及Patch进程)。issue需要以下面的格式:** ``` 异常类型:app运行时异常/编译异常 手机型号:如:Nexus 5(如是编译异常,则可以不填) 手机系统版本:如:Android 5.0 (如是编译异常,则可以不填) tinker版本:如:1.7.7 gradle版本:如:2.10 是否使用热更新SDK: 如 TinkerPatch SDK 或者 Bugly SDK 系统:如:Mac 堆栈/日志: 1. 如是编译异常,请在执行gradle命令时,加上--stacktrace; 2. 日志我们需要过滤"Tinker."关键字; 3. 对于合成失败的情况,请给出:patch进程的日志,这里需要将Android Moniter右上角设为No Filter。 ``` 提问题时若使用`不能用/没效果/有问题/报错`此类模糊表达,但又没给出任何代码截图报错的,将绝对不会有任何反馈。这种issue也是一律直接关闭的,大家可以参阅[提问的智慧](https://github.com/tvvocold/How-To-Ask-Questions-The-Smart-Way)。 Tinker是一个开源项目,希望大家遇到问题时要学会先思考,看看sample与Tinker的源码,更鼓励大家给我们提pr. ================================================ FILE: .gitignore ================================================ /build # Ignore Gradle GUI config gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar # Cache of project .gradletasknamecache .DS_Store node_modules # Built application files *.apk *.ap_ # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ *.iml .idea # Local configuration file (sdk path, etc) local.properties local.gradle # Proguard folder generated by Eclipse proguard/ # Log Files *.log /buildSdk ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Tinker Welcome to [report Issues](https://github.com/Tencent/tinker/issues) or [pull requests](https://github.com/Tencent/tinker/pulls). It's recommended to read the following Contributing Guide first before contributing. ## Issues We use Github Issues to track public bugs and feature requests. ### Search Known Issues First Please search the existing issues to see if any similar issue or feature request has already been filed. You should make sure your issue isn't redundant. ### Reporting New Issues If you open an issue, the more information the better. Such as detailed description, screenshot or video of your problem, logcat or code blocks for your crash. ## Pull Requests We strongly welcome your pull request to make Tinker better. ### Branch Management There are three main branches here: 1. `master` branch. 1. It is the latest (pre-)release branch. We use `master` for tags, with version number `1.1.0`, `1.2.0`, `1.3.0`... 2. **Don't submit any PR on `master` branch.** 2. `dev` branch. 1. It is our stable developing branch. After full testing, `dev` will be merged to `master` branch for the next release. 2. **You are recommended to submit bugfix or feature PR on `dev` branch.** 3. `hotfix` branch. 1. It is the latest tag version for hot fix. If we accept your pull request, we may just tag with version number `1.1.1`, `1.2.3`. 2. **Only submit urgent PR on `hotfix` branch for next specific release.** Normal bugfix or feature request should be submitted to `dev` branch. After full testing, we will merge them to `master` branch for the next release. If you have some urgent bugfixes on a published version, but the `master` branch have already far away with the latest tag version, you can submit a PR on hotfix. And it will be cherry picked to `dev` branch if it is possible. ``` master ↑ dev <--- hotfix PR ↑ feature/bugfix PR ``` ### Make Pull Requests The code team will monitor all pull request, we run some code check and test on it. After all tests passed, we will accecpt this PR. But it won't merge to `master` branch at once, which have some delay. Before submitting a pull request, please make sure the followings are done: 1. Fork the repo and create your branch from `master` or `hotfix`. 2. Update code or documentation if you have changed APIs. 3. Add the copyright notice to the top of any new files you've added. 4. Check your code lints and checkstyles. 5. Test and test again your code. 6. Now, you can submit your pull request on `dev` or `hotfix` branch. ## Code Style Guide Use [Code Style](https://github.com/Tencent/tinker/blob/master/checkstyle.xml) for Java and Android. * 4 spaces for indentation rather than tabs ## License By contributing to Tinker, you agree that your contributions will be licensed under its [BSD LICENSE](https://github.com/Tencent/tinker/blob/master/LICENSE) ================================================ FILE: LICENSE ================================================ Tencent is pleased to support the open source community by making Tinker available. Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. If you have downloaded a copy of the Tinker binary from Tencent, please note that the Tinker binary is licensed under the BSD 3-Clause License. If you have downloaded a copy of the Tinker source code from Tencent, please note that Tinker source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms. Your integration of Tinker into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within Tinker. A copy of the BSD 3-Clause License is included in this file. Other dependencies and licenses: ---------------------------------------------------------------------------------------- Open Source Software Licensed Under the Apache License, Version 2.0: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. Android Source Code 4.4_r1 Copyright (C) 2005-2015 The Android Open Source Project 2. buck v2016.04.18.01 Copyright 2014-present Facebook, Inc. 3. leakcanary v1.3.1 Copyright (C) 2014-2015 Square, Inc. Terms of the Apache License, Version 2.0: --------------------------------------------------- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: a) You must give any other recipients of the Work or Derivative Works a copy of this License; and b) You must cause any modified files to carry prominent notices stating that You changed the files;and c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works;and d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ---------------------------------------------------------------------------------------- Open Source Software Licensed Under the GNU Lesser General Public License, version 2.1 (LGPL-2.1): ---------------------------------------------------------------------------------------- 1. 7-Zip 16.02 7-Zip Copyright (C) 1999-2016 Igor Pavlov. Terms of the GNU Lesser General Public License, version 2.1 (LGPL-2.1): --------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are nformed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Sectio 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single ibrary together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest o this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! Open Source Software Licensed Under the BSD 2-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. bsdiff and bspatch 4.3 Copyright 2003-2005 Colin Percival All rights reserved Terms of the BSD 2-Clause License: --------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Open Source Software Licensed Under the BSD 3-Clause License: The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2016 THL A29 Limited. ---------------------------------------------------------------------------------------- 1. jbsdiff and jbspatch 0.1.1 Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) Based on a direct translation of bsdiff utility by Colin Percival and available at http://www.daemonology.net/bsdiff/ under the same license. Terms of the BSD 3-Clause License: -------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NO LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ ## Tinker [![license](http://img.shields.io/badge/license-BSD3-brightgreen.svg?style=flat)](https://github.com/Tencent/tinker/blob/master/LICENSE) [![Release Version](https://img.shields.io/badge/release-1.9.15.1-red.svg)](https://github.com/Tencent/tinker/releases) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/tinker/pulls) [![WeChat Approved](https://img.shields.io/badge/Wechat_Approved-1.9.15.1-red.svg)](https://github.com/Tencent/tinker/wiki) [中文说明](https://github.com/Tencent/tinker/wiki) Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstalling apk. ![tinker.png](assets/tinker.png) ## Getting started Add tinker-gradle-plugin as a dependency in your main `build.gradle` in the root of your project: ```gradle buildscript { dependencies { classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1') } } ``` Then you need to "apply" the plugin and add dependencies by adding the following lines to your `app/build.gradle`. ```gradle dependencies { //optional, help to generate the final application provided('com.tencent.tinker:tinker-android-anno:1.9.1') //tinker's main Android lib compile('com.tencent.tinker:tinker-android-lib:1.9.1') } ... ... apply plugin: 'com.tencent.tinker.patch' ``` If your app has a class that subclasses android.app.Application, then you need to modify that class, and move all its implements to [SampleApplicationLike](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java) rather than Application: ```java -public class YourApplication extends Application { +public class SampleApplicationLike extends DefaultApplicationLike { ``` Now you should change your `Application` class, make it a subclass of [TinkerApplication](https://github.com/Tencent/tinker/blob/master/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java). As you can see from its API, it is an abstract class that does not have a default constructor, so you must define a no-arg constructor: ```java public class SampleApplication extends TinkerApplication { public SampleApplication() { super( //tinkerFlags, which types is supported //dex only, library only, all support ShareConstants.TINKER_ENABLE_ALL, // This is passed as a string so the shell application does not // have a binary dependency on your ApplicationLifeCycle class. "tinker.sample.android.app.SampleApplicationLike"); } } ``` Use `tinker-android-anno` to generate your `Application` is recommended, you just need to add an annotation for your [SampleApplicationLike](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java) class ```java @DefaultLifeCycle( application = "tinker.sample.android.app.SampleApplication", //application name to generate flags = ShareConstants.TINKER_ENABLE_ALL) //tinkerFlags above public class SampleApplicationLike extends DefaultApplicationLike ``` How to install tinker? learn more at the sample [SampleApplicationLike](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/src/main/java/tinker/sample/android/app/SampleApplicationLike.java). For proguard, we have already made the proguard config automatic, and tinker will also generate the multiDex keep proguard file for you. For more tinker configurations, learn more at the sample [app/build.gradle](https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/build.gradle). ## Ark Support How to run tinker on the Ark? ### building patch Just use the following command: ```buildconfig bash build_patch_dexdiff.sh old=xxx new=xxx ``` * `old` indicates the absolute path of android apk(not compiled by Ark) with bugs * `new` indicates the absolute path of android apk(not compiled by Ark) with fixing The patch file is packaged in APK. ### compiling in Ark TODO At present it's compiled by Ark compiler team. The output patch is still packaged in APK format without signature. ### packaging the patch For tinker-cli, add the following lines to your `tinker_config.xml`. Otherwise, the default configure will be used. ```xml // path of patch // name of patch ``` For gradle, add the following lines to your `app/build.gradle`. Otherwise, the default configure will be used. ```gradle ark { path = "arkHot" // path of patch name = "patch.apk" // name of patch } ``` The patch is compiled by Ark and placed on the above path. all subsequent operations are same as tinker-cli or gradle. The ultimated patch APK consists of two patch files: * `classes.dex` for android * `patch.apk` with so for Ark. ## Tinker Known Issues There are some issues which Tinker can't dynamic update. 1. Can't update AndroidManifest.xml, such as add Android Component. 2. Do not support some Samsung models with os version android-21. 3. Due to Google Play Developer Distribution Agreement, we can't dynamic update our apk. ## Tinker Support Any problem? 1. Learn more from [tinker-sample-android](https://github.com/Tencent/tinker/tree/master/tinker-sample-android). 2. Read the [source code](https://github.com/Tencent/tinker/tree/master). 3. Read the [wiki](https://github.com/Tencent/tinker/wiki) or [FAQ](https://github.com/Tencent/tinker/wiki/Tinker-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) for help. 4. Contact us for help. ## Contributing For more information about contributing issues or pull requests, see our [Tinker Contributing Guide](https://github.com/Tencent/tinker/blob/master/CONTRIBUTING.md). ## License Tinker is under the BSD license. See the [LICENSE](https://github.com/Tencent/tinker/blob/master/LICENSE) file for details. ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { mavenLocal() mavenCentral() gradlePluginPortal() google() } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' classpath "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:4.0.4" } } allprojects { repositories { mavenLocal() mavenCentral() gradlePluginPortal() google() } tasks.withType(Javadoc).all { enabled = false options.setEncoding('UTF-8') } } ext { minSdkVersion = 10 compileSdkVersion = 29 targetSdkVersion = 23 buildToolsVersion = '28.0.3' androidXAnnotationVersion = '1.1.0' javaVersion = JavaVersion.VERSION_1_8 GROUP = 'com.tencent.tinker' VERSION_NAME = '1.9.15.2' POM_DESCRIPTION = 'Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstalling apk.' POM_URL = 'https://github.com/Tencent/tinker' POM_SCM_URL = 'https://github.com/Tencent/tinker.git' POM_LICENCE_NAME = 'BSD License' POM_LICENCE_URL = 'https://opensource.org/licenses/BSD-3-Clause' POM_LICENCE_DIST = 'repo' POM_DEVELOPER_ID = 'Tencent Wechat' POM_DEVELOPER_NAME = 'Tencent Wechat, Inc.' } apply from: rootProject.file('gradle/check.gradle') ================================================ FILE: checkstyle.xml ================================================ ================================================ FILE: findbugs-exclude.xml ================================================ ================================================ FILE: gradle/PublishArtifact.gradle ================================================ apply from: rootProject.file('gradle/WeChatPublish.gradle') def checkAndGetOption(name) { if (!project.hasProperty(name)) { throw new GradleException("Please specify '$name' option in gradle project properties.") } return project[name] } wechatPublish { pom { name = checkAndGetOption('POM_NAME') description = checkAndGetOption('POM_DESCRIPTION') url = checkAndGetOption('POM_URL') scm { url = checkAndGetOption('POM_SCM_URL') } licenses { license { name = checkAndGetOption('POM_LICENCE_NAME') url = checkAndGetOption('POM_LICENCE_URL') } } developers { developer { id = checkAndGetOption('POM_DEVELOPER_ID') name = checkAndGetOption('POM_DEVELOPER_NAME') } } } } ================================================ FILE: gradle/WeChatPublish.gradle ================================================ def extensionClass // Detect supported plugin modules if (plugins.hasPlugin('com.android.library')) { // Android library mode extensionClass = WeChatAndroidLibraryPublishExtension.class } else if (plugins.hasPlugin('java')) { // Java library mode extensionClass = WeChatJavaLibraryPublishExtension.class } else { // TODO: Support more languages throw new GradleException('This plugin must be applied after "java" or "com.android.library" plugin') } // Register wechatPublish extension extensions.create('wechatPublish', extensionClass, project) ext.artifactId = name class WeChatPublishExtension { boolean isSnapshot = true private String versionSuffix = '' protected boolean printModules = false boolean withJavadoc = true boolean withSources = true boolean withNativeSymbols = true boolean withDependencies = true boolean publishToBintray = false /* Deprecated */ boolean publishAllVariants = false Set publishVariants = [] boolean publishAllFlavors = true Set publishFlavors = [] String defaultFlavor private final Project project private boolean usedDefaultIsSnapshot = true private final ArrayList mavenPublishClosures = [] protected final ArrayList pomClosures = [] WeChatPublishExtension(Project proj) { project = proj fillDefaultConfiguration() proj.afterEvaluate { this.publish() } } protected final String uncapitalize(String str) { if (str == null || str.isEmpty()) { return str } return "" + Character.toLowerCase(str.charAt(0)) + str.substring(1) } protected void publish() { // Load local gradle script applyLocalScript() // Verify configuration values verifyPublishConfiguration() // Emit Maven DSL // Apply plugins if not done already if (!project.plugins.hasPlugin('maven-publish')) { project.plugins.apply('maven-publish') } mountAdditionalLogic(project) emitPublicationDSL(project) if (project.hasProperty('signingKeyId') || project.hasProperty('signing.keyId')) { if (!project.plugins.hasPlugin('signing')) { project.plugins.apply('signing') } emitSigningConfig(project) } emitRepositoryDSL(project) } void bintrayPackage(Closure cl) { // if (cl != null) // bintrayConfigureClosures << cl } void publishToMaven(Closure cl) { if (cl != null) mavenPublishClosures << cl } void pom(Closure cl) { if (cl != null) pomClosures << cl } void publishToBintray(Closure cl) { if (cl != null) { publishToBintray = true // bintrayConfigureClosures << cl } } String getFullVersion() { def ver = version + versionSuffix if (isSnapshot && !ver.endsWith('-SNAPSHOT')) ver += '-SNAPSHOT' return ver } protected void fillDefaultConfiguration() { isSnapshot = !project.rootProject.hasProperty('release') printModules = project.rootProject.hasProperty('printModules') if (project.rootProject.hasProperty('versionSuffix')) versionSuffix = project.rootProject.versionSuffix } private void applyLocalScript() { def localScriptFile if (project.rootProject.hasProperty('repoScript')) { // Repo script file specified using -PrepoScript=xxx argument, use that script. def repoScript = project.rootProject.property('repoScript') if (!repoScript.startsWith('/')) repoScript = "${System.getProperty('user.dir')}/${repoScript}" localScriptFile = new File(repoScript) } else { // No -PrepoScript=xxx argument, try 'local.gradle' in projectDir then rootProjectDir localScriptFile = project.file('local.gradle') if (!localScriptFile.file) localScriptFile = project.rootProject.file('local.gradle') } if (localScriptFile.file) { project.apply from: localScriptFile } } private void verifyPublishConfiguration() { // Warn default artifactId, groupId, version if (groupId.empty) { groupId = 'com.tencent.mm' System.err.println "groupId not specified, used default value: ${groupId}" } if (version == 'unspecified') { version = '0.1' System.err.println "version not specified, used default value: ${version}" } checkVersion() if (!usedDefaultIsSnapshot) { System.err.println 'isSnapshot should be avoided in build scripts.' } if (isSnapshot) { // Bintray does not allow SNAPSHOT publish publishToBintray = false } project.ext.fullVersion = fullVersion } private void checkVersion() { if (!(fullVersion ==~ /\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:\.\d+)?(?:-[\w-]+)?/)) { def message = "Invalid version: ${fullVersion}" if (!isSnapshot) throw new GradleException(message) System.err.println(message) } } final protected String getPublicationName() { String result = "" artifactId.split("[-_]").each { result += it.capitalize() } return uncapitalize(result) } protected void mountAdditionalLogic(project) {} protected void emitPublicationDSL(Project project) {} private void emitSigningConfig(Project project) { project.ext['signing.keyId'] = project.findProperty("signingKeyId") project.ext['signing.password'] = project.findProperty("signingPassword") project.ext['signing.secretKeyRingFile'] = project.findProperty("signingSecretKeyRingFile") project.signing { project.publishing.publications.all { publication -> sign publication } } } private void emitRepositoryDSL(Project project) { mavenPublishClosures.each { cl -> project.publishing.repositories { maven { cl.delegate = delegate cl() } } } if (publishToBintray) { System.err.println("[WeChatPublish] [W] 'publishToBintray' was deprecated and ignored now, consider migrate to MavenCentral instead.") } } String getArtifactId() { return project.artifactId } void setArtifactId(String id) { project.artifactId = id } String getGroupId() { return project.group } void setGroupId(String id) { project.group = id } String getVersion() { return project.version } void setVersion(String ver) { project.version = ver } void setIsSnapshot(boolean v) { isSnapshot = v usedDefaultIsSnapshot = false } } class WeChatJavaLibraryPublishExtension extends WeChatPublishExtension { WeChatJavaLibraryPublishExtension(Project project) { super(project) } @Override protected void mountAdditionalLogic(project) { // Print module description if needed if (printModules) { def anchorTask = project.tasks.findByName('compileJava') def printTask = project.task('printPublishArtifactInfo').doFirst { println "@@@WeChatPublish@@@ ${artifactId}: ${fullVersion}" } anchorTask.dependsOn printTask } } @Override protected void emitPublicationDSL(Project project) { def sourcesJarTask = project.task('sourcesJar', type: Jar) { classifier = 'sources' def srcDirs = [] def sources = project.sourceSets.main ['java', 'groovy', 'scala', 'kotlin'].each { if (sources.hasProperty(it)) srcDirs << sources[it].srcDirs } from srcDirs } def javadocTask = (project.tasks.findByName('javadoc') as Javadoc).with { title = null options { memberLevel = JavadocMemberLevel.PUBLIC def doclavaJar = project.rootProject.file('gradle/doclava-1.0.6.jar') if (doclavaJar.exists()) { doclet = 'com.google.doclava.Doclava' docletpath = [doclavaJar] } //docletpath = project.configurations.doclava.files as List } it } def javadocJarTask = project.task('javadocJar', type: Jar) { dependsOn javadocTask classifier = 'javadoc' from javadocTask.destinationDir } // TODO: upload javadoc to documentation site project.publishing.publications { "${publicationName}" (MavenPublication) { from project.components.java groupId this.groupId artifactId this.artifactId version this.fullVersion // Emit sourcesJar task if (withSources) { artifact sourcesJarTask } // Emit javadocJar task if (withJavadoc) { artifact javadocJarTask } } } pomClosures.each { cl -> project.publishing.publications { "${publicationName}"(MavenPublication) { pom cl } } } } } class WeChatAndroidLibraryPublishExtension extends WeChatPublishExtension { WeChatAndroidLibraryPublishExtension(Project project) { super(project) } @Override protected void mountAdditionalLogic(project) { // Print module description if needed if (printModules) { def anchorTask = project.tasks.findByName('preBuild') def printTask = project.task('printPublishArtifactInfo').doFirst { println "@@@WeChatPublish@@@ ${artifactId}: ${fullVersion}" } anchorTask.dependsOn printTask } } @Override protected void emitPublicationDSL(Project project) { HashSet emittedFlavors = new HashSet<>() def android = project.android def hasReleaseVariant = false android.libraryVariants.all { variant -> def variantName = variant.name def cVariantName = variantName.capitalize() def flavorName = variant.flavorName def variantOnlyName = uncapitalize(variantName.substring(flavorName.length(), variantName.length())) def hasFlavor = variant.flavorName != null && !variant.flavorName.isEmpty() if (flavorName == defaultFlavor) flavorName = '' def cFlavorName = flavorName.capitalize() if (!publishAllFlavors && !flavorName.empty && !publishFlavors.contains(flavorName)) return def generateSourcesTask = project.tasks.findByName("generate${cVariantName}Sources") def javadocTask = project.task("javadoc${cVariantName}", type: Javadoc) { group = 'documentation' title = null def classpathFiles = project.files(android.getBootClasspath().join(File.pathSeparator)) classpathFiles += project.files(project.configurations.compile) doFirst { classpath += classpathFiles } source = variant.javaCompile.source options { memberLevel = JavadocMemberLevel.PUBLIC def doclavaJar = project.rootProject.file('gradle/doclava-1.0.6.jar') if (doclavaJar.exists()) { doclet = 'com.google.doclava.Doclava' docletpath = [doclavaJar] } //docletpath = project.configurations.doclava.files as List } destinationDir = project.file("${project.buildDir}/docs/javadoc") exclude '**/BuildConfig.java' exclude '**/R.java' failOnError false dependsOn generateSourcesTask } def javadocJarTask = project.task("javadocJar${cVariantName}", type: Jar) { classifier = 'javadoc' from javadocTask.destinationDir dependsOn javadocTask } def sourcesJarTask = project.task("sourcesJar${cVariantName}", type: Jar) { classifier = 'sources' def srcDirs = [] variant.sourceSets.each { sources -> ['java', 'groovy', 'scala', 'kotlin'].each { if (sources.hasProperty(it)) srcDirs << sources[it].srcDirs } } from srcDirs dependsOn generateSourcesTask } def externalNativeBuildTask = project.tasks.findByName( "externalNativeBuild${cVariantName}") Zip nativeSymbolZipTask = null if (externalNativeBuildTask != null) { nativeSymbolZipTask = project.task("nativeSymbolZip${cVariantName}", type: Zip) { classifier = "${variantOnlyName}Symbols" from externalNativeBuildTask.objFolder include '*/*.so' dependsOn externalNativeBuildTask } externalNativeBuildTask.doLast { // If externalNativeBuild generates no shared library files, // remove symbols artifact from the publication. if (nativeSymbolZipTask.inputs.sourceFiles.empty) { def publication = project.publishing.publications .getByName("${publicationName}${cFlavorName}") publication.artifacts.removeIf { it.file == nativeSymbolZipTask.archivePath } } } } def bundleTask = project.tasks.findByName("bundle${cVariantName}Aar") if (bundleTask == null) bundleTask = project.tasks.findByName("bundle${cVariantName}") project.publishing.publications { "${publicationName}${cFlavorName}"(MavenPublication) { if (variantOnlyName == 'release') { hasReleaseVariant = true artifact(source: bundleTask, classifier: null) if (withSources) { artifact(source: sourcesJarTask, classifier: 'sources') } if (withJavadoc) { artifact(source: javadocJarTask, classifier: 'javadoc') } if (withNativeSymbols && nativeSymbolZipTask != null) { artifact(source: nativeSymbolZipTask, classifier: 'symbols') } } else if (publishAllVariants || publishVariants.contains(variantOnlyName)) { artifact(source: bundleTask, classifier: variantOnlyName) if (withNativeSymbols && nativeSymbolZipTask != null) { artifact(source: nativeSymbolZipTask, classifier: "${variantOnlyName}Symbols") } } if (!emittedFlavors.contains(flavorName)) { emittedFlavors << flavorName groupId this.groupId version this.fullVersion if (hasFlavor) { def currFlavor = variant.productFlavors.get(0) def actualArtifactIdSuffix = '' if (currFlavor.ext.has('artifactIdSuffix')) { actualArtifactIdSuffix = currFlavor.ext.artifactIdSuffix int firstNotBarPos = 0 while (firstNotBarPos < actualArtifactIdSuffix.length() && actualArtifactIdSuffix.charAt(firstNotBarPos) == '-') { ++firstNotBarPos } actualArtifactIdSuffix = actualArtifactIdSuffix.substring(firstNotBarPos) } else { actualArtifactIdSuffix = flavorName } artifactId actualArtifactIdSuffix.empty ? this.artifactId : "${this.artifactId}-${actualArtifactIdSuffix}" } else { artifactId this.artifactId } pom { packaging 'aar' withXml { // Resolve dependencies final depsNode = asNode().appendNode('dependencies') final addDep = { Dependency dep, String scope -> if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified") return // ignore invalid dependencies // Determine actual artifactId for the dependency def artifactId = dep.name def version = dep.version if (dep instanceof ProjectDependency) { def p = (dep as ProjectDependency).dependencyProject if (p.hasProperty('artifactId')) artifactId = p.property('artifactId') if (p.hasProperty('fullVersion')) version = p.property('fullVersion') } def node = depsNode.appendNode('dependency') node.appendNode('groupId', dep.group) node.appendNode('artifactId', artifactId) node.appendNode('version', version) node.appendNode('scope', scope) if (!dep.transitive) { // If this dependency is transitive, we should force exclude all its dependencies them from the POM final exclusionNode = node.appendNode('exclusions').appendNode('exclusion') exclusionNode.appendNode('groupId', '*') exclusionNode.appendNode('artifactId', '*') } else if (!dep.properties.excludeRules.empty) { // Otherwise add specified exclude rules final exclusions = node.appendNode('exclusions') dep.properties.excludeRules.each { ExcludeRule rule -> def exclusionNode = exclusions.appendNode('exclusion') exclusionNode.appendNode('groupId', rule.group ?: '*') exclusionNode.appendNode('artifactId', rule.module ?: '*') } } } if (withDependencies) { def visitedDeps = [] as Set [ 'compile': 'compile', 'api': 'compile', 'implementation': 'compile', 'runtimeOnly': 'runtime', 'provided': 'runtime' ].each { conf, scope -> if (project.configurations.find { it.name.equals(conf) }) { project.configurations[conf].allDependencies.each { if (visitedDeps.contains(it)) { return } addDep(it, scope) visitedDeps.add(it) } } } } } } } } } pomClosures.each { cl -> project.publishing.publications { "${publicationName}${cFlavorName}"(MavenPublication) { pom cl } } } } // android.libraryVariants.all // Check whether "release" variant is published project.afterEvaluate { if (!hasReleaseVariant) throw new GradleException('Publishing Android library require "release" variant') } } } ================================================ FILE: gradle/check.gradle ================================================ if (project == rootProject) { task('checkAll') { group 'verification' doFirst { logger.lifecycle 'Run checkAll task' } outputs.upToDateWhen { false } } task('checkIncremental') { group 'verification' doFirst { logger.lifecycle 'Run checkIncremental task' } outputs.upToDateWhen { false } } return } // ---------- // Changed Files // ---------- def reportsDir = "${rootProject.buildDir}/reports/${project.name}" def projectHeader = project.path.replaceAll(':', '/').substring(1) logger.lifecycle "Analyze code of module '${project.path}':" logger.lifecycle "Reading changed files..." if (!project.ext.has('filesChanged')) { project.ext.filesChanged = [] if ("jenkins" == System.env.BUILDER_MODEL) { logger.lifecycle 'Check MR changed files' def inputFile = 'changed_file_list.txt' if (project.hasProperty('changed_files')) { inputFile = project.properties['changed_files'] } def file = rootProject.file(inputFile) if (file.exists()) { file.eachLine { if (it.startsWith(projectHeader)) project.ext.filesChanged << it } } } else { logger.lifecycle 'Check git-diff changed files' String command = "git diff --name-only --diff-filter=ACMRTUXB ${projectHeader}" String changeInfo = command.execute(null, rootProject.rootDir).text.trim() if (changeInfo && !changeInfo.isEmpty()) { project.ext.filesChanged = changeInfo.split("\n").toList() } } } if (filesChanged.size() == 0) { logger.lifecycle "No files changed found, skip analyzing." } else { logger.lifecycle "Files changed found:" filesChanged.each { project.logger.lifecycle it } } // ---------- // CheckStyle, :checkstyleAll, :checkstyleIncremental // ---------- def checkStyleConfig = rootProject.file('checkstyle.xml') if (checkStyleConfig.exists()) { if (!project.plugins.hasPlugin('checkstyle')) { apply plugin: 'checkstyle' } checkstyle { configFile checkStyleConfig configProperties.checkstyleSuppressionsPath = rootProject.file("suppressions.xml").absolutePath toolVersion '8.19' ignoreFailures true showViolations true } task('checkstyleAll', type: Checkstyle) { group 'verification' classpath = files() source 'src/main' include '**/*.java' exclude '**/gen/**' reports { html { enabled = false } xml { enabled = true destination file("$reportsDir/checkstyle.xml") } } outputs.upToDateWhen { false } } if (filesChanged.size > 0) { def task = task('checkstyleIncremental', type: Checkstyle) { group 'verification' classpath = files() source 'src' exclude '**/gen/**' exclude '**/test/**' exclude '**/*.gradle' exclude '**/proto/*.java' exclude '**/protobuf/*.java' exclude '**/com/google/**/*.java' for (String item: filesChanged) { if (item.contains('src/')) { include "**/${item.substring(item.lastIndexOf('src/') + 'src/'.length())}" } } reports { html { enabled = false } xml { enabled = true destination file("$reportsDir/checkstyle.xml") } } outputs.upToDateWhen { false } } } project.afterEvaluate { tasks.findByName('checkstyleAll')?.with { rootProject.tasks.findByName('checkAll')?.finalizedBy(it) } tasks.findByName('checkstyleIncremental')?.with { rootProject.tasks.findByName('checkIncremental')?.finalizedBy(it) } } } else { logger.lifecycle "Can not find CheckStyle config file, skip." } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true android.useAndroidX=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: pmd-ruleset.xml ================================================ This ruleset was created from PMD.rul ================================================ FILE: settings.gradle ================================================ include ':tinker-commons' include ':tinker-android:tinker-android-loader' include ':tinker-android:tinker-android-loader-no-op' include ':tinker-android:tinker-android-lib' include ':tinker-android:tinker-android-lib-no-op' include ':tinker-android:tinker-android-anno' include ':tinker-android:tinker-android-anno-support' include ':tinker-build:tinker-patch-cli' include ':tinker-build:tinker-patch-lib' include ':tinker-build:tinker-patch-gradle-plugin' include ':third-party:aosp-dexutils' include ':third-party:bsdiff-util' include ':third-party:tinker-ziputils' ================================================ FILE: suppressions.xml ================================================ ================================================ FILE: third-party/aosp-dexutils/.gitignore ================================================ /build ================================================ FILE: third-party/aosp-dexutils/NOTICE ================================================ Original work Copyright (c) 2005-2008, The Android Open Source Project Modified work Copyright (C) 2016 THL A29 Limited, a Tencent company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: third-party/aosp-dexutils/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from('build/libs') { include '*.jar' exclude '*javadoc.jar' exclude '*-sources.jar' } into(rootProject.file("buildSdk/android")) } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: third-party/aosp-dexutils/gradle.properties ================================================ POM_ARTIFACT_ID=aosp-dexutils POM_NAME=Dex Utils Lib From AOSP POM_PACKAGING=jar ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Annotation.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; import static com.tencent.tinker.android.dex.EncodedValueReader.ENCODED_ANNOTATION; /** * An annotation. */ public final class Annotation extends Item { public byte visibility; public EncodedValue encodedAnnotation; public Annotation(int off, byte visibility, EncodedValue encodedAnnotation) { super(off); this.visibility = visibility; this.encodedAnnotation = encodedAnnotation; } public EncodedValueReader getReader() { return new EncodedValueReader(encodedAnnotation, ENCODED_ANNOTATION); } public int getTypeIndex() { EncodedValueReader reader = getReader(); reader.readAnnotation(); return reader.getAnnotationType(); } @Override public int compareTo(Annotation other) { int cmpRes = encodedAnnotation.compareTo(other.encodedAnnotation); if (cmpRes != 0) return cmpRes; return CompareUtils.uCompare(visibility, other.visibility); } @Override public int hashCode() { return HashCodeHelper.hash(visibility, encodedAnnotation); } @Override public boolean equals(Object obj) { if (!(obj instanceof Annotation)) { return false; } return this.compareTo((Annotation) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UBYTE + encodedAnnotation.byteCountInDex(); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSet.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section; import com.tencent.tinker.android.dex.util.CompareUtils; import java.util.Arrays; /** * *** This file is NOT a part of AOSP. *** * * Structure of AnnotationSet element in Dex file. */ public class AnnotationSet extends Section.Item { public int[] annotationOffsets; public AnnotationSet(int off, int[] annotationOffsets) { super(off); this.annotationOffsets = annotationOffsets; } @Override public int compareTo(AnnotationSet other) { int size = annotationOffsets.length; int oSize = other.annotationOffsets.length; if (size != oSize) { return CompareUtils.uCompare(size, oSize); } for (int i = 0; i < size; ++i) { if (annotationOffsets[i] != other.annotationOffsets[i]) { return CompareUtils.uCompare(annotationOffsets[i], other.annotationOffsets[i]); } } return 0; } @Override public int hashCode() { return Arrays.hashCode(annotationOffsets); } @Override public boolean equals(Object obj) { if (!(obj instanceof AnnotationSet)) { return false; } return this.compareTo((AnnotationSet) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UINT * (1 + annotationOffsets.length); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationSetRefList.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section; import com.tencent.tinker.android.dex.util.CompareUtils; import java.util.Arrays; /** * *** This file is NOT a part of AOSP. *** * * Structure of AnnotationSetRefList element in Dex file. */ public class AnnotationSetRefList extends Section.Item { public int[] annotationSetRefItems; public AnnotationSetRefList(int off, int[] annotationSetRefItems) { super(off); this.annotationSetRefItems = annotationSetRefItems; } @Override public int compareTo(AnnotationSetRefList other) { int size = annotationSetRefItems.length; int oSize = other.annotationSetRefItems.length; if (size != oSize) { return CompareUtils.uCompare(size, oSize); } for (int i = 0; i < size; ++i) { if (annotationSetRefItems[i] != other.annotationSetRefItems[i]) { return CompareUtils.uCompare(annotationSetRefItems[i], other.annotationSetRefItems[i]); } } return 0; } @Override public int hashCode() { return Arrays.hashCode(annotationSetRefItems); } @Override public boolean equals(Object obj) { if (!(obj instanceof AnnotationSetRefList)) { return false; } return this.compareTo((AnnotationSetRefList) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UINT * (1 + annotationSetRefItems.length); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/AnnotationsDirectory.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; /** * *** This file is NOT a part of AOSP. *** * * Structure of AnnotationsDirectory element in Dex file. */ public class AnnotationsDirectory extends Section.Item { public int classAnnotationsOffset; /** * fieldAnnotations[][2]; * fieldAnnotations[i][0]: fieldIndex, fieldAnnotations[i][1]: annotation set Offset */ public int[][] fieldAnnotations; /** * methodAnnotations[][2]; * methodAnnotations[i][0]: methodIndex, methodAnnotations[i][1]: annotation set Offset */ public int[][] methodAnnotations; /** * parameterAnnotations[][2]; * parameterAnnotations[i][0]: methodIndex, parameterAnnotations[i][1]: annotation set reflist Offset */ public int[][] parameterAnnotations; public AnnotationsDirectory( int off, int classAnnotationsOffset, int[][] fieldAnnotations, int[][] methodAnnotations, int[][] parameterAnnotations ) { super(off); this.classAnnotationsOffset = classAnnotationsOffset; this.fieldAnnotations = fieldAnnotations; this.methodAnnotations = methodAnnotations; this.parameterAnnotations = parameterAnnotations; } @Override public int compareTo(AnnotationsDirectory other) { if (classAnnotationsOffset != other.classAnnotationsOffset) { return CompareUtils.uCompare(classAnnotationsOffset, other.classAnnotationsOffset); } int fieldsSize = fieldAnnotations.length; int methodsSize = methodAnnotations.length; int parameterListSize = parameterAnnotations.length; int oFieldsSize = other.fieldAnnotations.length; int oMethodsSize = other.methodAnnotations.length; int oParameterListSize = other.parameterAnnotations.length; if (fieldsSize != oFieldsSize) { return CompareUtils.sCompare(fieldsSize, oFieldsSize); } if (methodsSize != oMethodsSize) { return CompareUtils.sCompare(methodsSize, oMethodsSize); } if (parameterListSize != oParameterListSize) { return CompareUtils.sCompare(parameterListSize, oParameterListSize); } for (int i = 0; i < fieldsSize; ++i) { int fieldIdx = fieldAnnotations[i][0]; int annotationOffset = fieldAnnotations[i][1]; int othFieldIdx = other.fieldAnnotations[i][0]; int othAnnotationOffset = other.fieldAnnotations[i][1]; if (fieldIdx != othFieldIdx) { return CompareUtils.uCompare(fieldIdx, othFieldIdx); } if (annotationOffset != othAnnotationOffset) { return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); } } for (int i = 0; i < methodsSize; ++i) { int methodIdx = methodAnnotations[i][0]; int annotationOffset = methodAnnotations[i][1]; int othMethodIdx = other.methodAnnotations[i][0]; int othAnnotationOffset = other.methodAnnotations[i][1]; if (methodIdx != othMethodIdx) { return CompareUtils.uCompare(methodIdx, othMethodIdx); } if (annotationOffset != othAnnotationOffset) { return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); } } for (int i = 0; i < parameterListSize; ++i) { int methodIdx = parameterAnnotations[i][0]; int annotationOffset = parameterAnnotations[i][1]; int othMethodIdx = other.parameterAnnotations[i][0]; int othAnnotationOffset = other.parameterAnnotations[i][1]; if (methodIdx != othMethodIdx) { return CompareUtils.uCompare(methodIdx, othMethodIdx); } if (annotationOffset != othAnnotationOffset) { return CompareUtils.sCompare(annotationOffset, othAnnotationOffset); } } return 0; } @Override public int hashCode() { return HashCodeHelper.hash(classAnnotationsOffset, fieldAnnotations, methodAnnotations, parameterAnnotations); } @Override public boolean equals(Object obj) { if (!(obj instanceof AnnotationsDirectory)) { return false; } return this.compareTo((AnnotationsDirectory) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UINT * (4 + 2 * (fieldAnnotations.length + methodAnnotations.length + parameterAnnotations.length)); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/CallSiteId.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; public class CallSiteId extends Item { public int offset; public CallSiteId(int off, int offset) { super(off); this.offset = offset; } public void writeTo(Dex.Section out) { out.writeInt(offset); } @Override public int byteCountInDex() { return SizeOf.UINT; } @Override public int compareTo(CallSiteId o) { return CompareUtils.uCompare(offset, o.offset); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassData.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; public final class ClassData extends Item { public Field[] staticFields; public Field[] instanceFields; public Method[] directMethods; public Method[] virtualMethods; public ClassData(int off, Field[] staticFields, Field[] instanceFields, Method[] directMethods, Method[] virtualMethods) { super(off); this.staticFields = staticFields; this.instanceFields = instanceFields; this.directMethods = directMethods; this.virtualMethods = virtualMethods; } @Override public int compareTo(ClassData other) { int res = CompareUtils.aArrCompare(staticFields, other.staticFields); if (res != 0) { return res; } res = CompareUtils.aArrCompare(instanceFields, other.instanceFields); if (res != 0) { return res; } res = CompareUtils.aArrCompare(directMethods, other.directMethods); if (res != 0) { return res; } return CompareUtils.aArrCompare(virtualMethods, other.virtualMethods); } @Override public int hashCode() { return HashCodeHelper.hash(staticFields, instanceFields, directMethods, virtualMethods); } @Override public boolean equals(Object obj) { if (!(obj instanceof ClassData)) { return false; } return this.compareTo((ClassData) obj) == 0; } @Override public int byteCountInDex() { int res = Leb128.unsignedLeb128Size(staticFields.length); res += Leb128.unsignedLeb128Size(instanceFields.length); res += Leb128.unsignedLeb128Size(directMethods.length); res += Leb128.unsignedLeb128Size(virtualMethods.length); res += calcFieldsSize(staticFields); res += calcFieldsSize(instanceFields); res += calcMethodsSize(directMethods); res += calcMethodsSize(virtualMethods); return res; } private int calcFieldsSize(Field[] fields) { int res = 0; int prevFieldIndex = 0; for (Field field : fields) { int fieldIndexDelta = field.fieldIndex - prevFieldIndex; prevFieldIndex = field.fieldIndex; res += Leb128.unsignedLeb128Size(fieldIndexDelta) + Leb128.unsignedLeb128Size(field.accessFlags); } return res; } private int calcMethodsSize(Method[] methods) { int res = 0; int prevMethodIndex = 0; for (Method method : methods) { int methodIndexDelta = method.methodIndex - prevMethodIndex; prevMethodIndex = method.methodIndex; res += Leb128.unsignedLeb128Size(methodIndexDelta) + Leb128.unsignedLeb128Size(method.accessFlags) + Leb128.unsignedLeb128Size(method.codeOffset); } return res; } public static class Field implements Comparable { public int fieldIndex; public int accessFlags; public Field(int fieldIndex, int accessFlags) { this.fieldIndex = fieldIndex; this.accessFlags = accessFlags; } @Override public int compareTo(Field other) { int res = CompareUtils.uCompare(fieldIndex, other.fieldIndex); if (res != 0) { return res; } return CompareUtils.sCompare(accessFlags, other.accessFlags); } @Override public boolean equals(Object obj) { if (!(obj instanceof Field)) { return false; } return this.compareTo((Field) obj) == 0; } @Override public int hashCode() { return HashCodeHelper.hash(fieldIndex, accessFlags); } } public static class Method implements Comparable { public int methodIndex; public int accessFlags; public int codeOffset; public Method(int methodIndex, int accessFlags, int codeOffset) { this.methodIndex = methodIndex; this.accessFlags = accessFlags; this.codeOffset = codeOffset; } @Override public int compareTo(Method other) { int res = CompareUtils.uCompare(methodIndex, other.methodIndex); if (res != 0) { return res; } res = CompareUtils.sCompare(accessFlags, other.accessFlags); if (res != 0) { return res; } return CompareUtils.sCompare(codeOffset, other.codeOffset); } @Override public boolean equals(Object obj) { if (!(obj instanceof Method)) { return false; } return this.compareTo((Method) obj) == 0; } @Override public int hashCode() { return HashCodeHelper.hash(methodIndex, accessFlags, codeOffset); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/ClassDef.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; /** * A type definition. */ public final class ClassDef extends TableOfContents.Section.Item { public static final int NO_INDEX = -1; public static final int NO_OFFSET = 0; public int typeIndex; public int accessFlags; public int supertypeIndex; public int interfacesOffset; public int sourceFileIndex; public int annotationsOffset; public int classDataOffset; public int staticValuesOffset; public ClassDef(int off, int typeIndex, int accessFlags, int supertypeIndex, int interfacesOffset, int sourceFileIndex, int annotationsOffset, int classDataOffset, int staticValuesOffset) { super(off); this.typeIndex = typeIndex; this.accessFlags = accessFlags; this.supertypeIndex = supertypeIndex; this.interfacesOffset = interfacesOffset; this.sourceFileIndex = sourceFileIndex; this.annotationsOffset = annotationsOffset; this.classDataOffset = classDataOffset; this.staticValuesOffset = staticValuesOffset; } @Override public int compareTo(ClassDef other) { int res = CompareUtils.uCompare(typeIndex, other.typeIndex); if (res != 0) { return res; } res = CompareUtils.sCompare(accessFlags, other.accessFlags); if (res != 0) { return res; } res = CompareUtils.uCompare(supertypeIndex, other.supertypeIndex); if (res != 0) { return res; } res = CompareUtils.sCompare(interfacesOffset, other.interfacesOffset); if (res != 0) { return res; } res = CompareUtils.uCompare(sourceFileIndex, other.sourceFileIndex); if (res != 0) { return res; } res = CompareUtils.sCompare(annotationsOffset, other.annotationsOffset); if (res != 0) { return res; } res = CompareUtils.sCompare(classDataOffset, other.classDataOffset); if (res != 0) { return res; } return CompareUtils.sCompare(staticValuesOffset, other.staticValuesOffset); } @Override public int hashCode() { return HashCodeHelper.hash(typeIndex, accessFlags, supertypeIndex, interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, staticValuesOffset); } @Override public boolean equals(Object obj) { if (!(obj instanceof ClassDef)) { return false; } return this.compareTo((ClassDef) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.CLASS_DEF_ITEM; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Code.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; public final class Code extends Item { public int registersSize; public int insSize; public int outsSize; public int debugInfoOffset; public short[] instructions; public Try[] tries; public CatchHandler[] catchHandlers; public Code(int off, int registersSize, int insSize, int outsSize, int debugInfoOffset, short[] instructions, Try[] tries, CatchHandler[] catchHandlers) { super(off); this.registersSize = registersSize; this.insSize = insSize; this.outsSize = outsSize; this.debugInfoOffset = debugInfoOffset; this.instructions = instructions; this.tries = tries; this.catchHandlers = catchHandlers; } @Override public int compareTo(Code other) { int res = CompareUtils.sCompare(registersSize, other.registersSize); if (res != 0) { return res; } res = CompareUtils.sCompare(insSize, other.insSize); if (res != 0) { return res; } res = CompareUtils.sCompare(outsSize, other.outsSize); if (res != 0) { return res; } res = CompareUtils.sCompare(debugInfoOffset, other.debugInfoOffset); if (res != 0) { return res; } res = CompareUtils.uArrCompare(instructions, other.instructions); if (res != 0) { return res; } res = CompareUtils.aArrCompare(tries, other.tries); if (res != 0) { return res; } return CompareUtils.aArrCompare(catchHandlers, other.catchHandlers); } @Override public int hashCode() { return HashCodeHelper.hash(registersSize, insSize, outsSize, debugInfoOffset, instructions, tries, catchHandlers); } @Override public boolean equals(Object obj) { if (!(obj instanceof Code)) { return false; } return this.compareTo((Code) obj) == 0; } @Override public int byteCountInDex() { int insnsSize = instructions.length; int res = 4 * SizeOf.USHORT + 2 * SizeOf.UINT + insnsSize * SizeOf.USHORT; if (tries.length > 0) { if ((insnsSize & 1) == 1) { res += SizeOf.USHORT; } res += tries.length * SizeOf.TRY_ITEM; int catchHandlerSize = catchHandlers.length; res += Leb128.unsignedLeb128Size(catchHandlerSize); for (CatchHandler catchHandler : catchHandlers) { int typeIdxAddrPairCount = catchHandler.typeIndexes.length; if (catchHandler.catchAllAddress != -1) { res += Leb128.signedLeb128Size(-typeIdxAddrPairCount) + Leb128.unsignedLeb128Size(catchHandler.catchAllAddress); } else { res += Leb128.signedLeb128Size(typeIdxAddrPairCount); } for (int i = 0; i < typeIdxAddrPairCount; ++i) { res += Leb128.unsignedLeb128Size(catchHandler.typeIndexes[i]) + Leb128.unsignedLeb128Size(catchHandler.addresses[i]); } } } return res; } public static class Try implements Comparable { public int startAddress; public int instructionCount; public int catchHandlerIndex; public Try(int startAddress, int instructionCount, int catchHandlerIndex) { this.startAddress = startAddress; this.instructionCount = instructionCount; this.catchHandlerIndex = catchHandlerIndex; } @Override public int compareTo(Try other) { int res = CompareUtils.sCompare(startAddress, other.startAddress); if (res != 0) { return res; } res = CompareUtils.sCompare(instructionCount, other.instructionCount); if (res != 0) { return res; } return CompareUtils.sCompare(catchHandlerIndex, other.catchHandlerIndex); } } public static class CatchHandler implements Comparable { public int[] typeIndexes; public int[] addresses; public int catchAllAddress; public int offset; public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress, int offset) { this.typeIndexes = typeIndexes; this.addresses = addresses; this.catchAllAddress = catchAllAddress; this.offset = offset; } @Override public int compareTo(CatchHandler other) { int res = CompareUtils.sArrCompare(typeIndexes, other.typeIndexes); if (res != 0) { return res; } res = CompareUtils.sArrCompare(addresses, other.addresses); if (res != 0) { return res; } return CompareUtils.sCompare(catchAllAddress, other.catchAllAddress); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DebugInfoItem.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; /** * *** This file is NOT a part of AOSP. *** * * Structure of DebugInfoItem element in Dex file. */ public class DebugInfoItem extends Item { public static final byte DBG_END_SEQUENCE = 0x00; public static final byte DBG_ADVANCE_PC = 0x01; public static final byte DBG_ADVANCE_LINE = 0x02; public static final byte DBG_START_LOCAL = 0x03; public static final byte DBG_START_LOCAL_EXTENDED = 0x04; public static final byte DBG_END_LOCAL = 0x05; public static final byte DBG_RESTART_LOCAL = 0x06; public static final byte DBG_SET_PROLOGUE_END = 0x07; public static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; public static final byte DBG_SET_FILE = 0x09; public int lineStart; public int[] parameterNames; public byte[] infoSTM; public DebugInfoItem(int off, int lineStart, int[] parameterNames, byte[] infoSTM) { super(off); this.lineStart = lineStart; this.parameterNames = parameterNames; this.infoSTM = infoSTM; } @Override public int compareTo(DebugInfoItem o) { int origLineStart = lineStart; int destLineStart = o.lineStart; if (origLineStart != destLineStart) { return origLineStart - destLineStart; } int cmpRes = CompareUtils.uArrCompare(parameterNames, o.parameterNames); if (cmpRes != 0) return cmpRes; cmpRes = CompareUtils.uArrCompare(infoSTM, o.infoSTM); return cmpRes; } @Override public int hashCode() { return HashCodeHelper.hash(lineStart, parameterNames, infoSTM); } @Override public boolean equals(Object obj) { if (!(obj instanceof DebugInfoItem)) { return false; } return this.compareTo((DebugInfoItem) obj) == 0; } @Override public int byteCountInDex() { int byteCount = Leb128.unsignedLeb128Size(lineStart) + Leb128.unsignedLeb128Size(parameterNames.length); for (int pn : parameterNames) { byteCount += Leb128.unsignedLeb128p1Size(pn); } byteCount += infoSTM.length * SizeOf.UBYTE; return byteCount; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Dex.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.android.dex.util.FileUtils; import com.tencent.tinker.android.dx.util.Hex; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.AbstractList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.RandomAccess; import java.util.zip.Adler32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * The bytes of a dex file in memory for reading and writing. All int offsets * are unsigned. */ public final class Dex { // Provided as a convenience to avoid a memory allocation to benefit Dalvik. // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik. static final short[] EMPTY_SHORT_ARRAY = new short[0]; private static final int CHECKSUM_OFFSET = 8; private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + SizeOf.CHECKSUM; public static final int ACC_PUBLIC = 0x1; public static final int ACC_PRIVATE = 0x2; public static final int ACC_PROTECTED = 0x4; public static final int ACC_STATIC = 0x8; public static final int ACC_FINAL = 0x10; public static final int ACC_SYNCHRONIZED = 0x20; public static final int ACC_VOLATILE = 0x40; public static final int ACC_BRIDGE = 0x40; public static final int ACC_TRANSIENT = 0x80; public static final int ACC_VARARGS = 0x80; public static final int ACC_NATIVE = 0x100; public static final int ACC_INTERFACE = 0x200; public static final int ACC_ABSTRACT = 0x400; public static final int ACC_STRICT = 0x800; public static final int ACC_SYNTHETIC = 0x1000; public static final int ACC_ANNOTATION = 0x2000; public static final int ACC_ENUM = 0x4000; public static final int ACC_CONSTRUCTOR = 0x10000; public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000; private final TableOfContents tableOfContents = new TableOfContents(); private final StringTable strings = new StringTable(); private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable(); private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable(); private final ProtoIdTable protoIds = new ProtoIdTable(); private final FieldIdTable fieldIds = new FieldIdTable(); private final MethodIdTable methodIds = new MethodIdTable(); private final CallSiteIdTable callsiteIds = new CallSiteIdTable(); private final MethodHandleTable methodHandles = new MethodHandleTable(); private final ClassDefTable classDefs = new ClassDefTable(); private ByteBuffer data; private int nextSectionStart = 0; private byte[] signature = null; /** * Creates a new dex that reads from {@code data}. It is an error to modify * {@code data} after using it to create a dex buffer. */ public Dex(byte[] data) throws IOException { this(ByteBuffer.wrap(data)); } private Dex(ByteBuffer data) throws IOException { this.data = data; this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.readFrom(this); } /** * Creates a new empty dex of the specified size. */ public Dex(int byteCount) { this.data = ByteBuffer.wrap(new byte[byteCount]); this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.fileSize = byteCount; } /** * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. */ public Dex(InputStream in) throws IOException { loadFrom(in); } public Dex(InputStream in, int initSize) throws IOException { loadFrom(in, initSize); } /** * Creates a new dex buffer from the dex file {@code file}. */ public Dex(File file) throws IOException { if (file == null) { throw new IllegalArgumentException("file is null."); } if (FileUtils.hasArchiveSuffix(file.getName())) { ZipFile zipFile = null; try { zipFile = new ZipFile(file); ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); if (entry != null) { InputStream inputStream = null; try { inputStream = zipFile.getInputStream(entry); loadFrom(inputStream, (int) entry.getSize()); } finally { if (inputStream != null) { inputStream.close(); } } } else { throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); } } finally { if (zipFile != null) { try { zipFile.close(); } catch (Exception e) { // ignored. } } } } else if (file.getName().endsWith(".dex")) { InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); loadFrom(in, (int) file.length()); } catch (Exception e) { throw new DexException(e); } finally { if (in != null) { try { in.close(); } catch (Exception e) { // ignored. } } } } else { throw new DexException("unknown output extension: " + file); } } private static void checkBounds(int index, int length) { if (index < 0 || index >= length) { throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); } } private void loadFrom(InputStream in) throws IOException { loadFrom(in, 0); } private void loadFrom(InputStream in, int initSize) throws IOException { byte[] rawData = FileUtils.readStream(in, initSize); this.data = ByteBuffer.wrap(rawData); this.data.order(ByteOrder.LITTLE_ENDIAN); this.tableOfContents.readFrom(this); } public void writeTo(OutputStream out) throws IOException { byte[] rawData = data.array(); out.write(rawData); out.flush(); } public void writeTo(File dexOut) throws IOException { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(dexOut)); writeTo(out); } catch (Exception e) { throw new DexException(e); } finally { if (out != null) { try { out.close(); } catch (Exception e) { // ignored. } } } } public TableOfContents getTableOfContents() { return tableOfContents; } /** * IMPORTANT To open a dex section by {@code TableOfContents.Section}, * please use {@code openSection(TableOfContents.Section tocSec)} instead of * passing tocSec.off to this method. * * Because dex section returned by this method never checks * tocSec's bound when reading or writing data. */ public Section openSection(int position) { if (position < 0 || position >= data.capacity()) { throw new IllegalArgumentException( "position=" + position + " length=" + data.capacity() ); } ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(position); sectionData.limit(data.capacity()); return new Section("temp-section", sectionData); } public Section openSection(TableOfContents.Section tocSec) { int position = tocSec.off; if (position < 0 || position >= data.capacity()) { throw new IllegalArgumentException( "position=" + position + " length=" + data.capacity() ); } ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(position); sectionData.limit(position + tocSec.byteCount); return new Section("section", sectionData); } public Section appendSection(int maxByteCount, String name) { int limit = nextSectionStart + maxByteCount; ByteBuffer sectionData = data.duplicate(); sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? sectionData.position(nextSectionStart); sectionData.limit(limit); Section result = new Section(name, sectionData); nextSectionStart = limit; return result; } public int getLength() { return data.capacity(); } public int getNextSectionStart() { return nextSectionStart; } /** * Returns a copy of the the bytes of this dex. */ public byte[] getBytes() { ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe byte[] result = new byte[data.capacity()]; data.position(0); data.get(result); return result; } public List strings() { return strings; } public List typeIds() { return typeIds; } public List typeNames() { return typeNames; } public List protoIds() { return protoIds; } public List fieldIds() { return fieldIds; } public List methodIds() { return methodIds; } public List callsiteIds() { return callsiteIds; } public List methodHandles() { return methodHandles; } public List classDefs() { return classDefs; } public Iterable classDefIterable() { return new ClassDefIterable(); } public ClassData readClassData(ClassDef classDef) { int offset = classDef.classDataOffset; if (offset == 0) { throw new IllegalArgumentException("offset == 0"); } return openSection(offset).readClassData(); } public Code readCode(ClassData.Method method) { int offset = method.codeOffset; if (offset == 0) { throw new IllegalArgumentException("offset == 0"); } return openSection(offset).readCode(); } /** * Returns the signature of all but the first 32 bytes of this dex. The * first 32 bytes of dex files are not specified to be included in the * signature. */ public byte[] computeSignature(boolean forceRecompute) { if (this.signature != null) { if (!forceRecompute) { return this.signature; } } MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { throw new AssertionError(); } byte[] buffer = new byte[8192]; ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe data.limit(data.capacity()); data.position(SIGNATURE_OFFSET + SizeOf.SIGNATURE); while (data.hasRemaining()) { int count = Math.min(buffer.length, data.remaining()); data.get(buffer, 0, count); digest.update(buffer, 0, count); } return (this.signature = digest.digest()); } private String bytesToHexString(byte[] bytes) { StringBuilder strBuilder = new StringBuilder(bytes.length << 1); for (byte b : bytes) { strBuilder.append(Hex.u1(b)); } return strBuilder.toString(); } /** * Returns the checksum of all but the first 12 bytes of {@code dex}. */ public int computeChecksum() throws IOException { Adler32 adler32 = new Adler32(); byte[] buffer = new byte[8192]; ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe data.limit(data.capacity()); data.position(CHECKSUM_OFFSET + SizeOf.CHECKSUM); while (data.hasRemaining()) { int count = Math.min(buffer.length, data.remaining()); data.get(buffer, 0, count); adler32.update(buffer, 0, count); } return (int) adler32.getValue(); } /** * Generates the signature and checksum of the dex file {@code out} and * writes them to the file. */ public void writeHashes() throws IOException { openSection(SIGNATURE_OFFSET).write(computeSignature(true)); openSection(CHECKSUM_OFFSET).writeInt(computeChecksum()); } /** * Look up a field id name index from a field index. Cheaper than: * {@code fieldIds().get(fieldDexIndex).getNameIndex();} */ public int nameIndexFromFieldIndex(int fieldIndex) { checkBounds(fieldIndex, tableOfContents.fieldIds.size); int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); position += SizeOf.USHORT; // declaringClassIndex position += SizeOf.USHORT; // typeIndex return data.getInt(position); // nameIndex } public int findStringIndex(String s) { return Collections.binarySearch(strings, s); } public int findTypeIndex(String descriptor) { return Collections.binarySearch(typeNames, descriptor); } public int findFieldIndex(FieldId fieldId) { return Collections.binarySearch(fieldIds, fieldId); } public int findMethodIndex(MethodId methodId) { return Collections.binarySearch(methodIds, methodId); } public int findClassDefIndexFromTypeIndex(int typeIndex) { checkBounds(typeIndex, tableOfContents.typeIds.size); if (!tableOfContents.classDefs.exists()) { return -1; } for (int i = 0; i < tableOfContents.classDefs.size; i++) { if (typeIndexFromClassDefIndex(i) == typeIndex) { return i; } } return -1; } /** * Look up a field id type index from a field index. Cheaper than: * {@code fieldIds().get(fieldDexIndex).getTypeIndex();} */ public int typeIndexFromFieldIndex(int fieldIndex) { checkBounds(fieldIndex, tableOfContents.fieldIds.size); int position = tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * fieldIndex); position += SizeOf.USHORT; // declaringClassIndex return data.getShort(position) & 0xFFFF; // typeIndex } /** * Look up a method id declaring class index from a method index. Cheaper than: * {@code methodIds().get(methodIndex).getDeclaringClassIndex();} */ public int declaringClassIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); return data.getShort(position) & 0xFFFF; // declaringClassIndex } /** * Look up a method id name index from a method index. Cheaper than: * {@code methodIds().get(methodIndex).getNameIndex();} */ public int nameIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex position += SizeOf.USHORT; // protoIndex return data.getInt(position); // nameIndex } /** * Look up a parameter type ids from a method index. Cheaper than: * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} */ public short[] parameterTypeIndicesFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex int protoIndex = data.getShort(position) & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex position += SizeOf.UINT; // returnTypeIndex int parametersOffset = data.getInt(position); if (parametersOffset == 0) { return EMPTY_SHORT_ARRAY; } position = parametersOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected parameter type list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } /** * Look up a parameter type ids from a methodId. Cheaper than: * {@code readTypeList(protoIds.get(methodIds().get(methodDexIndex).getProtoIndex()).getParametersOffset()).getTypes();} */ public short[] parameterTypeIndicesFromMethodId(MethodId methodId) { int protoIndex = methodId.protoIndex & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); int position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex position += SizeOf.UINT; // returnTypeIndex int parametersOffset = data.getInt(position); if (parametersOffset == 0) { return EMPTY_SHORT_ARRAY; } position = parametersOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected parameter type list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } /** * Look up a method id return type index from a method index. Cheaper than: * {@code protoIds().get(methodIds().get(methodDexIndex).getProtoIndex()).getReturnTypeIndex();} */ public int returnTypeIndexFromMethodIndex(int methodIndex) { checkBounds(methodIndex, tableOfContents.methodIds.size); int position = tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * methodIndex); position += SizeOf.USHORT; // declaringClassIndex int protoIndex = data.getShort(position) & 0xFFFF; checkBounds(protoIndex, tableOfContents.protoIds.size); position = tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * protoIndex); position += SizeOf.UINT; // shortyIndex return data.getInt(position); // returnTypeIndex } /** * Look up a descriptor index from a type index. Cheaper than: * {@code openSection(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();} */ public int descriptorIndexFromTypeIndex(int typeIndex) { checkBounds(typeIndex, tableOfContents.typeIds.size); int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex); return data.getInt(position); } /** * Look up a type index index from a class def index. */ public int typeIndexFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); return data.getInt(position); } /** * Look up an annotation directory offset from a class def index. */ public int annotationDirectoryOffsetFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); position += SizeOf.UINT; // type position += SizeOf.UINT; // accessFlags position += SizeOf.UINT; // superType position += SizeOf.UINT; // interfacesOffset position += SizeOf.UINT; // sourceFileIndex return data.getInt(position); } /** * Look up interface types indices from a return type index from a method index. Cheaper than: * {@code ...getClassDef(classDefIndex).getInterfaces();} */ public short[] interfaceTypeIndicesFromClassDefIndex(int classDefIndex) { checkBounds(classDefIndex, tableOfContents.classDefs.size); int position = tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * classDefIndex); position += SizeOf.UINT; // type position += SizeOf.UINT; // accessFlags position += SizeOf.UINT; // superType int interfacesOffset = data.getInt(position); if (interfacesOffset == 0) { return EMPTY_SHORT_ARRAY; } position = interfacesOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected interfaces list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } public short[] interfaceTypeIndicesFromClassDef(ClassDef classDef) { int interfacesOffset = classDef.interfacesOffset; if (interfacesOffset == 0) { return EMPTY_SHORT_ARRAY; } int position = interfacesOffset; int size = data.getInt(position); if (size <= 0) { throw new AssertionError("Unexpected interfaces list size: " + size); } position += SizeOf.UINT; short[] types = new short[size]; for (int i = 0; i < size; i++) { types[i] = data.getShort(position); position += SizeOf.USHORT; } return types; } public final class Section extends DexDataBuffer { private final String name; private Section(String name, ByteBuffer data) { super(data); this.name = name; } /** * @inheritDoc */ @Override public StringData readStringData() { ensureFourBytesAligned(tableOfContents.stringDatas, false); return super.readStringData(); } /** * @inheritDoc */ @Override public TypeList readTypeList() { ensureFourBytesAligned(tableOfContents.typeLists, false); return super.readTypeList(); } /** * @inheritDoc */ @Override public FieldId readFieldId() { ensureFourBytesAligned(tableOfContents.fieldIds, false); return super.readFieldId(); } /** * @inheritDoc */ @Override public MethodId readMethodId() { ensureFourBytesAligned(tableOfContents.methodIds, false); return super.readMethodId(); } /** * @inheritDoc */ @Override public ProtoId readProtoId() { ensureFourBytesAligned(tableOfContents.protoIds, false); return super.readProtoId(); } /** * @inheritDoc */ @Override public ClassDef readClassDef() { ensureFourBytesAligned(tableOfContents.classDefs, false); return super.readClassDef(); } /** * @inheritDoc */ @Override public Code readCode() { ensureFourBytesAligned(tableOfContents.codes, false); return super.readCode(); } /** * @inheritDoc */ @Override public DebugInfoItem readDebugInfoItem() { ensureFourBytesAligned(tableOfContents.debugInfos, false); return super.readDebugInfoItem(); } /** * @inheritDoc */ @Override public ClassData readClassData() { ensureFourBytesAligned(tableOfContents.classDatas, false); return super.readClassData(); } /** * @inheritDoc */ @Override public Annotation readAnnotation() { ensureFourBytesAligned(tableOfContents.annotations, false); return super.readAnnotation(); } /** * @inheritDoc */ @Override public AnnotationSet readAnnotationSet() { ensureFourBytesAligned(tableOfContents.annotationSets, false); return super.readAnnotationSet(); } /** * @inheritDoc */ @Override public AnnotationSetRefList readAnnotationSetRefList() { ensureFourBytesAligned(tableOfContents.annotationSetRefLists, false); return super.readAnnotationSetRefList(); } /** * @inheritDoc */ @Override public AnnotationsDirectory readAnnotationsDirectory() { ensureFourBytesAligned(tableOfContents.annotationsDirectories, false); return super.readAnnotationsDirectory(); } /** * @inheritDoc */ @Override public EncodedValue readEncodedArray() { ensureFourBytesAligned(tableOfContents.encodedArrays, false); return super.readEncodedArray(); } private void ensureFourBytesAligned(TableOfContents.Section tocSec, boolean isFillWithZero) { if (tocSec.isElementFourByteAligned) { if (isFillWithZero) { alignToFourBytesWithZeroFill(); } else { alignToFourBytes(); } } } /** * @inheritDoc */ @Override public int writeStringData(StringData stringData) { ensureFourBytesAligned(tableOfContents.stringDatas, true); return super.writeStringData(stringData); } /** * @inheritDoc */ @Override public int writeTypeList(TypeList typeList) { ensureFourBytesAligned(tableOfContents.typeLists, true); return super.writeTypeList(typeList); } /** * @inheritDoc */ @Override public int writeFieldId(FieldId fieldId) { ensureFourBytesAligned(tableOfContents.fieldIds, true); return super.writeFieldId(fieldId); } /** * @inheritDoc */ @Override public int writeMethodId(MethodId methodId) { ensureFourBytesAligned(tableOfContents.methodIds, true); return super.writeMethodId(methodId); } /** * @inheritDoc */ @Override public int writeProtoId(ProtoId protoId) { ensureFourBytesAligned(tableOfContents.protoIds, true); return super.writeProtoId(protoId); } /** * @inheritDoc */ @Override public int writeClassDef(ClassDef classDef) { ensureFourBytesAligned(tableOfContents.classDefs, true); return super.writeClassDef(classDef); } /** * @inheritDoc */ @Override public int writeCode(Code code) { ensureFourBytesAligned(tableOfContents.codes, true); return super.writeCode(code); } /** * @inheritDoc */ @Override public int writeDebugInfoItem(DebugInfoItem debugInfoItem) { ensureFourBytesAligned(tableOfContents.debugInfos, true); return super.writeDebugInfoItem(debugInfoItem); } /** * @inheritDoc */ @Override public int writeClassData(ClassData classData) { ensureFourBytesAligned(tableOfContents.classDatas, true); return super.writeClassData(classData); } /** * @inheritDoc */ @Override public int writeAnnotation(Annotation annotation) { ensureFourBytesAligned(tableOfContents.annotations, true); return super.writeAnnotation(annotation); } /** * @inheritDoc */ @Override public int writeAnnotationSet(AnnotationSet annotationSet) { ensureFourBytesAligned(tableOfContents.annotationSets, true); return super.writeAnnotationSet(annotationSet); } /** * @inheritDoc */ @Override public int writeAnnotationSetRefList(AnnotationSetRefList annotationSetRefList) { ensureFourBytesAligned(tableOfContents.annotationSetRefLists, true); return super.writeAnnotationSetRefList(annotationSetRefList); } /** * @inheritDoc */ @Override public int writeAnnotationsDirectory(AnnotationsDirectory annotationsDirectory) { ensureFourBytesAligned(tableOfContents.annotationsDirectories, true); return super.writeAnnotationsDirectory(annotationsDirectory); } /** * @inheritDoc */ @Override public int writeEncodedArray(EncodedValue encodedValue) { ensureFourBytesAligned(tableOfContents.encodedArrays, true); return super.writeEncodedArray(encodedValue); } } private final class StringTable extends AbstractList implements RandomAccess { @Override public String get(int index) { checkBounds(index, tableOfContents.stringIds.size); int stringOff = openSection(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)).readInt(); return openSection(stringOff).readStringData().value; } @Override public int size() { return tableOfContents.stringIds.size; } } private final class TypeIndexToDescriptorIndexTable extends AbstractList implements RandomAccess { @Override public Integer get(int index) { return descriptorIndexFromTypeIndex(index); } @Override public int size() { return tableOfContents.typeIds.size; } } private final class TypeIndexToDescriptorTable extends AbstractList implements RandomAccess { @Override public String get(int index) { return strings.get(descriptorIndexFromTypeIndex(index)); } @Override public int size() { return tableOfContents.typeIds.size; } } private final class ProtoIdTable extends AbstractList implements RandomAccess { @Override public ProtoId get(int index) { checkBounds(index, tableOfContents.protoIds.size); return openSection(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) .readProtoId(); } @Override public int size() { return tableOfContents.protoIds.size; } } private final class FieldIdTable extends AbstractList implements RandomAccess { @Override public FieldId get(int index) { checkBounds(index, tableOfContents.fieldIds.size); return openSection(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) .readFieldId(); } @Override public int size() { return tableOfContents.fieldIds.size; } } private final class MethodIdTable extends AbstractList implements RandomAccess { @Override public MethodId get(int index) { checkBounds(index, tableOfContents.methodIds.size); return openSection(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) .readMethodId(); } @Override public int size() { return tableOfContents.methodIds.size; } } private final class CallSiteIdTable extends AbstractList implements RandomAccess { @Override public CallSiteId get(int index) { checkBounds(index, tableOfContents.callSiteIds.size); return openSection(tableOfContents.callSiteIds.off + (SizeOf.CALLSITE_ID_ITEM * index)) .readCallSiteId(); } @Override public int size() { return tableOfContents.callSiteIds.size; } } private final class MethodHandleTable extends AbstractList implements RandomAccess { @Override public MethodHandle get(int index) { checkBounds(index, tableOfContents.methodHandles.size); return openSection(tableOfContents.methodHandles.off + (SizeOf.METHOD_HANDLE_ITEM * index)) .readMethodHandle(); } @Override public int size() { return tableOfContents.methodHandles.size; } } private final class ClassDefTable extends AbstractList implements RandomAccess { @Override public ClassDef get(int index) { checkBounds(index, tableOfContents.classDefs.size); return openSection(tableOfContents.classDefs.off + (SizeOf.CLASS_DEF_ITEM * index)) .readClassDef(); } @Override public int size() { return tableOfContents.classDefs.size; } } private final class ClassDefIterator implements Iterator { private final Section in = openSection(tableOfContents.classDefs); private int count = 0; @Override public boolean hasNext() { return count < tableOfContents.classDefs.size; } @Override public ClassDef next() { if (!hasNext()) { throw new NoSuchElementException(); } count++; return in.readClassDef(); } @Override public void remove() { throw new UnsupportedOperationException(); } } private final class ClassDefIterable implements Iterable { public Iterator iterator() { return !tableOfContents.classDefs.exists() ? Collections.emptySet().iterator() : new ClassDefIterator(); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexException.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; /** * Thrown when there's a format problem reading, writing, or generally * processing a dex file. */ public class DexException extends RuntimeException { static final long serialVersionUID = 1L; public DexException(String message) { super(message); } public DexException(Throwable cause) { super(cause); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/DexFormat.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; /** * Constants that show up in and are otherwise related to {@code .dex} * files, and helper methods for same. */ public final class DexFormat { private DexFormat() {} /** API level to target in order to allow spaces in SimpleName */ public static final int API_SPACES_IN_SIMPLE_NAME = 10000; /** API level to target in order to generate const-method-handle and const-method-type */ public static final int API_CONST_METHOD_HANDLE = 28; /** API level to target in order to generate invoke-polymorphic and invoke-custom */ public static final int API_METHOD_HANDLES = 26; /** API level to target in order to define default and static interface methods */ public static final int API_DEFINE_INTERFACE_METHODS = 24; /** API level to target in order to invoke default and static interface methods */ public static final int API_INVOKE_INTERFACE_METHODS = 24; /** API level at which the invocation of static interface methods is permitted by dx. * This value has been determined experimentally by testing on different VM versions. */ public static final int API_INVOKE_STATIC_INTERFACE_METHODS = 21; /** API level to target in order to suppress extended opcode usage */ public static final int API_NO_EXTENDED_OPCODES = 13; /** * API level to target in order to produce the most modern file * format */ public static final int API_CURRENT = API_CONST_METHOD_HANDLE; /** dex file version number for API level 10000 and earlier */ public static final String VERSION_FOR_API_10000 = "040"; /** dex file version number for API level 28 and earlier */ public static final String VERSION_FOR_API_28 = "039"; /** dex file version number for API level 26 and earlier */ public static final String VERSION_FOR_API_26 = "038"; /** dex file version number for API level 24 and earlier */ public static final String VERSION_FOR_API_24 = "037"; /** dex file version number for API level 13 and earlier */ public static final String VERSION_FOR_API_13 = "035"; /** * Dex file version number for dalvik. *

* Note: Dex version 36 was loadable in some versions of Dalvik but was never fully supported or * completed and is not considered a valid dex file format. *

*/ public static final String VERSION_CURRENT = VERSION_FOR_API_28; /** * file name of the primary {@code .dex} file inside an * application or library {@code .jar} file */ public static final String DEX_IN_JAR_NAME = "classes.dex"; /** common prefix for all dex file "magic numbers" */ public static final String MAGIC_PREFIX = "dex\n"; /** common suffix for all dex file "magic numbers" */ public static final String MAGIC_SUFFIX = "\0"; /** * value used to indicate endianness of file contents */ public static final int ENDIAN_TAG = 0x12345678; /** * Maximum addressable field or method index. * The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or * meth@CCCC. */ public static final int MAX_MEMBER_IDX = 0xFFFF; /** * Maximum addressable type index. * The largest addressable type is 0xffff, in the "instruction formats" spec as type@CCCC. */ public static final int MAX_TYPE_IDX = 0xFFFF; /** * Returns the API level corresponding to the given magic number, * or {@code -1} if the given array is not a well-formed dex file * magic number. * * @param magic array of bytes containing DEX file magic string * @return API level corresponding to magic string if valid, -1 otherwise. */ public static int magicToApi(byte[] magic) { if (magic.length != 8) { return -1; } if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') || (magic[7] != '\0')) { return -1; } String version = "" + ((char) magic[4]) + ((char) magic[5]) +((char) magic[6]); if (version.equals(VERSION_FOR_API_13)) { return API_NO_EXTENDED_OPCODES; } else if (version.equals(VERSION_FOR_API_24)) { return API_DEFINE_INTERFACE_METHODS; } else if (version.equals(VERSION_FOR_API_26)) { return API_METHOD_HANDLES; } else if (version.equals(VERSION_FOR_API_28)) { return API_CONST_METHOD_HANDLE; } else if (version.equals(VERSION_FOR_API_10000)) { return API_SPACES_IN_SIMPLE_NAME; } else if (version.equals(VERSION_CURRENT)) { return API_CURRENT; } return -1; } /** * Returns the magic number corresponding to the given target API level. * * @param targetApiLevel level of API (minimum supported value 13). * @return Magic string corresponding to API level supplied. */ public static String apiToMagic(int targetApiLevel) { String version; if (targetApiLevel >= API_CURRENT) { version = VERSION_CURRENT; } else if (targetApiLevel >= API_SPACES_IN_SIMPLE_NAME) { version = VERSION_FOR_API_10000; } else if (targetApiLevel >= API_CONST_METHOD_HANDLE) { version = VERSION_FOR_API_28; } else if (targetApiLevel >= API_METHOD_HANDLES) { version = VERSION_FOR_API_26; } else if (targetApiLevel >= API_DEFINE_INTERFACE_METHODS) { version = VERSION_FOR_API_24; } else { version = VERSION_FOR_API_13; } return MAGIC_PREFIX + version + MAGIC_SUFFIX; } /** * Checks whether a DEX file magic string is supported. * @param magic string from DEX file * @return */ public static boolean isSupportedDexMagic(byte[] magic) { int api = magicToApi(magic); return api > 0; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValue.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.ByteInput; import com.tencent.tinker.android.dex.util.CompareUtils; import java.util.Arrays; import static com.tencent.tinker.android.dex.TableOfContents.Section.Item; /** * An encoded value or array. */ public final class EncodedValue extends Item { public byte[] data; public EncodedValue(int off, byte[] data) { super(off); this.data = data; } public ByteInput asByteInput() { return new ByteInput() { private int position = 0; @Override public byte readByte() { return data[position++]; } }; } @Override public int compareTo(EncodedValue other) { return CompareUtils.uArrCompare(data, other.data); } @Override public int hashCode() { return Arrays.hashCode(data); } @Override public boolean equals(Object obj) { if (!(obj instanceof EncodedValue)) { return false; } return this.compareTo((EncodedValue) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UBYTE * data.length; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueCodec.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.ByteInput; import com.tencent.tinker.android.dex.util.ByteOutput; /** * Read and write {@code encoded_value} primitives. */ public final class EncodedValueCodec { private EncodedValueCodec() { } /** * Writes a signed integral to {@code out}. */ public static void writeSignedIntegralValue(ByteOutput out, int type, long value) { /* * Figure out how many bits are needed to represent the value, * including a sign bit: The bit count is subtracted from 65 * and not 64 to account for the sign bit. The xor operation * has the effect of leaving non-negative values alone and * unary complementing negative values (so that a leading zero * count always returns a useful number for our present * purpose). */ int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); // Round up the requiredBits to a number of bytes. int requiredBytes = (requiredBits + 0x07) >> 3; /* * Write the header byte, which includes the type and * requiredBytes - 1. */ out.writeByte(type | ((requiredBytes - 1) << 5)); // Write the value, per se. while (requiredBytes > 0) { out.writeByte((byte) value); value >>= 8; requiredBytes--; } } /** * Writes an unsigned integral to {@code out}. */ public static void writeUnsignedIntegralValue(ByteOutput out, int type, long value) { // Figure out how many bits are needed to represent the value. int requiredBits = 64 - Long.numberOfLeadingZeros(value); if (requiredBits == 0) { requiredBits = 1; } // Round up the requiredBits to a number of bytes. int requiredBytes = (requiredBits + 0x07) >> 3; /* * Write the header byte, which includes the type and * requiredBytes - 1. */ out.writeByte(type | ((requiredBytes - 1) << 5)); // Write the value, per se. while (requiredBytes > 0) { out.writeByte((byte) value); value >>= 8; requiredBytes--; } } /** * Writes a right-zero-extended value to {@code out}. */ public static void writeRightZeroExtendedValue(ByteOutput out, int type, long value) { // Figure out how many bits are needed to represent the value. int requiredBits = 64 - Long.numberOfTrailingZeros(value); if (requiredBits == 0) { requiredBits = 1; } // Round up the requiredBits to a number of bytes. int requiredBytes = (requiredBits + 0x07) >> 3; // Scootch the first bits to be written down to the low-order bits. value >>= 64 - (requiredBytes * 8); /* * Write the header byte, which includes the type and * requiredBytes - 1. */ out.writeByte(type | ((requiredBytes - 1) << 5)); // Write the value, per se. while (requiredBytes > 0) { out.writeByte((byte) value); value >>= 8; requiredBytes--; } } /** * Read a signed integer. * * @param zwidth byte count minus one */ public static int readSignedInt(ByteInput in, int zwidth) { int result = 0; for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xff) << 24); } result >>= (3 - zwidth) * 8; return result; } /** * Read an unsigned integer. * * @param zwidth byte count minus one * @param fillOnRight true to zero fill on the right; false on the left */ public static int readUnsignedInt(ByteInput in, int zwidth, boolean fillOnRight) { int result = 0; if (!fillOnRight) { for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xff) << 24); } result >>>= (3 - zwidth) * 8; } else { for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xff) << 24); } } return result; } /** * Read a signed long. * * @param zwidth byte count minus one */ public static long readSignedLong(ByteInput in, int zwidth) { long result = 0; for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); } result >>= (7 - zwidth) * 8; return result; } /** * Read an unsigned long. * * @param zwidth byte count minus one * @param fillOnRight true to zero fill on the right; false on the left */ public static long readUnsignedLong(ByteInput in, int zwidth, boolean fillOnRight) { long result = 0; if (!fillOnRight) { for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); } result >>>= (7 - zwidth) * 8; } else { for (int i = zwidth; i >= 0; i--) { result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); } } return result; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/EncodedValueReader.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.ByteInput; /** * Pull parser for encoded values. */ public final class EncodedValueReader { public static final int ENCODED_BYTE = 0x00; public static final int ENCODED_SHORT = 0x02; public static final int ENCODED_CHAR = 0x03; public static final int ENCODED_INT = 0x04; public static final int ENCODED_LONG = 0x06; public static final int ENCODED_FLOAT = 0x10; public static final int ENCODED_DOUBLE = 0x11; public static final int ENCODED_METHOD_TYPE = 0x15; public static final int ENCODED_METHOD_HANDLE = 0x16; public static final int ENCODED_STRING = 0x17; public static final int ENCODED_TYPE = 0x18; public static final int ENCODED_FIELD = 0x19; public static final int ENCODED_ENUM = 0x1b; public static final int ENCODED_METHOD = 0x1a; public static final int ENCODED_ARRAY = 0x1c; public static final int ENCODED_ANNOTATION = 0x1d; public static final int ENCODED_NULL = 0x1e; public static final int ENCODED_BOOLEAN = 0x1f; /** placeholder type if the type is not yet known */ private static final int MUST_READ = -1; protected final ByteInput in; private int type = MUST_READ; private int annotationType; private int arg; public EncodedValueReader(ByteInput in) { this.in = in; } public EncodedValueReader(EncodedValue in) { this(in.asByteInput()); } /** * Creates a new encoded value reader whose only value is the specified * known type. This is useful for encoded values without a type prefix, * such as class_def_item's encoded_array or annotation_item's * encoded_annotation. */ public EncodedValueReader(ByteInput in, int knownType) { this.in = in; this.type = knownType; } public EncodedValueReader(EncodedValue in, int knownType) { this(in.asByteInput(), knownType); } /** * Returns the type of the next value to read. */ public int peek() { if (type == MUST_READ) { int argAndType = in.readByte() & 0xff; type = argAndType & 0x1f; arg = (argAndType & 0xe0) >> 5; } return type; } /** * Begins reading the elements of an array, returning the array's size. The * caller must follow up by calling a read method for each element in the * array. For example, this reads a byte array:
   {@code
     *   int arraySize = readArray();
     *   for (int i = 0, i < arraySize; i++) {
     *     readByte();
     *   }
     * }
*/ public int readArray() { checkType(ENCODED_ARRAY); type = MUST_READ; return Leb128.readUnsignedLeb128(in); } /** * Begins reading the fields of an annotation, returning the number of * fields. The caller must follow up by making alternating calls to {@link * #readAnnotationName()} and another read method. For example, this reads * an annotation whose fields are all bytes:
   {@code
     *   int fieldCount = readAnnotation();
     *   int annotationType = getAnnotationType();
     *   for (int i = 0; i < fieldCount; i++) {
     *       readAnnotationName();
     *       readByte();
     *   }
     * }
*/ public int readAnnotation() { checkType(ENCODED_ANNOTATION); type = MUST_READ; annotationType = Leb128.readUnsignedLeb128(in); return Leb128.readUnsignedLeb128(in); } /** * Returns the type of the annotation just returned by {@link * #readAnnotation()}. This method's value is undefined unless the most * recent call was to {@link #readAnnotation()}. */ public int getAnnotationType() { return annotationType; } public int readAnnotationName() { return Leb128.readUnsignedLeb128(in); } public byte readByte() { checkType(ENCODED_BYTE); type = MUST_READ; return (byte) EncodedValueCodec.readSignedInt(in, arg); } public short readShort() { checkType(ENCODED_SHORT); type = MUST_READ; return (short) EncodedValueCodec.readSignedInt(in, arg); } public char readChar() { checkType(ENCODED_CHAR); type = MUST_READ; return (char) EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readInt() { checkType(ENCODED_INT); type = MUST_READ; return EncodedValueCodec.readSignedInt(in, arg); } public long readLong() { checkType(ENCODED_LONG); type = MUST_READ; return EncodedValueCodec.readSignedLong(in, arg); } public float readFloat() { checkType(ENCODED_FLOAT); type = MUST_READ; return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true)); } public double readDouble() { checkType(ENCODED_DOUBLE); type = MUST_READ; return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true)); } public int readMethodType() { checkType(ENCODED_METHOD_TYPE); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readMethodHandle() { checkType(ENCODED_METHOD_HANDLE); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readString() { checkType(ENCODED_STRING); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readType() { checkType(ENCODED_TYPE); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readField() { checkType(ENCODED_FIELD); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readEnum() { checkType(ENCODED_ENUM); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public int readMethod() { checkType(ENCODED_METHOD); type = MUST_READ; return EncodedValueCodec.readUnsignedInt(in, arg, false); } public void readNull() { checkType(ENCODED_NULL); type = MUST_READ; } public boolean readBoolean() { checkType(ENCODED_BOOLEAN); type = MUST_READ; return arg != 0; } /** * Skips a single value, including its nested values if it is an array or * annotation. */ public void skipValue() { switch (peek()) { case ENCODED_BYTE: readByte(); break; case ENCODED_SHORT: readShort(); break; case ENCODED_CHAR: readChar(); break; case ENCODED_INT: readInt(); break; case ENCODED_LONG: readLong(); break; case ENCODED_FLOAT: readFloat(); break; case ENCODED_DOUBLE: readDouble(); break; case ENCODED_METHOD_TYPE: readMethodType(); break; case ENCODED_METHOD_HANDLE: readMethodHandle(); break; case ENCODED_STRING: readString(); break; case ENCODED_TYPE: readType(); break; case ENCODED_FIELD: readField(); break; case ENCODED_ENUM: readEnum(); break; case ENCODED_METHOD: readMethod(); break; case ENCODED_ARRAY: for (int i = 0, size = readArray(); i < size; i++) { skipValue(); } break; case ENCODED_ANNOTATION: for (int i = 0, size = readAnnotation(); i < size; i++) { readAnnotationName(); skipValue(); } break; case ENCODED_NULL: readNull(); break; case ENCODED_BOOLEAN: readBoolean(); break; default: throw new DexException("Unexpected type: " + Integer.toHexString(type)); } } private void checkType(int expected) { if (peek() != expected) { throw new IllegalStateException( String.format("Expected %x but was %x", expected, peek())); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/FieldId.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; public final class FieldId extends Item { public int declaringClassIndex; public int typeIndex; public int nameIndex; public FieldId(int off, int declaringClassIndex, int typeIndex, int nameIndex) { super(off); this.declaringClassIndex = declaringClassIndex; this.typeIndex = typeIndex; this.nameIndex = nameIndex; } public int compareTo(FieldId other) { if (declaringClassIndex != other.declaringClassIndex) { return CompareUtils.uCompare(declaringClassIndex, other.declaringClassIndex); } if (nameIndex != other.nameIndex) { return CompareUtils.uCompare(nameIndex, other.nameIndex); } return CompareUtils.uCompare(typeIndex, other.typeIndex); // should always be 0 } @Override public int hashCode() { return HashCodeHelper.hash(declaringClassIndex, typeIndex, nameIndex); } @Override public boolean equals(Object obj) { if (!(obj instanceof FieldId)) { return false; } return this.compareTo((FieldId) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.MEMBER_ID_ITEM; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Leb128.java ================================================ /* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.ByteInput; import com.tencent.tinker.android.dex.util.ByteOutput; /** * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 * section 7.6. */ public final class Leb128 { private Leb128() { } /** * Gets the number of bytes in the unsigned LEB128 encoding of the * given value. * * @param value the value in question * @return its write size, in bytes */ public static int unsignedLeb128Size(int value) { // TODO: This could be much cleverer. int remaining = value >>> 7; int count = 0; while (remaining != 0) { remaining >>>= 7; count++; } return count + 1; } public static int unsignedLeb128p1Size(int value) { return unsignedLeb128Size(value + 1); } /** * Gets the number of bytes in the signed LEB128 encoding of the * given value. * * @param value the value in question * @return its write size, in bytes */ public static int signedLeb128Size(int value) { // TODO: This could be much cleverer. int remaining = value >> 7; int count = 0; boolean hasMore = true; int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; while (hasMore) { hasMore = (remaining != end) || ((remaining & 1) != ((value >> 6) & 1)); value = remaining; remaining >>= 7; count++; } return count; } /** * Reads an signed integer from {@code in}. */ public static int readSignedLeb128(ByteInput in) { int result = 0; int cur; int count = 0; int signBits = -1; do { cur = in.readByte() & 0xff; result |= (cur & 0x7f) << (count * 7); signBits <<= 7; count++; } while (((cur & 0x80) == 0x80) && count < 5); if ((cur & 0x80) == 0x80) { throw new DexException("invalid LEB128 sequence"); } // Sign extend if appropriate if (((signBits >> 1) & result) != 0) { result |= signBits; } return result; } /** * Reads an unsigned leb128 integer from {@code in}. */ public static int readUnsignedLeb128(ByteInput in) { int result = 0; int cur; int count = 0; do { cur = in.readByte() & 0xff; result |= (cur & 0x7f) << (count * 7); count++; } while (((cur & 0x80) == 0x80) && count < 5); if ((cur & 0x80) == 0x80) { throw new DexException("invalid LEB128 sequence"); } return result; } /** * Reads an unsigned leb128p1 integer from {@code in}. */ public static int readUnsignedLeb128p1(ByteInput in) { return readUnsignedLeb128(in) - 1; } /** * Writes {@code value} as an unsigned leb128 integer to {@code out}, starting at * {@code offset}. Returns the number of bytes written. */ public static int writeUnsignedLeb128(ByteOutput out, int value) { int remaining = value >>> 7; int bytesWritten = 0; while (remaining != 0) { out.writeByte((byte) ((value & 0x7f) | 0x80)); ++bytesWritten; value = remaining; remaining >>>= 7; } out.writeByte((byte) (value & 0x7f)); ++bytesWritten; return bytesWritten; } /** * Writes {@code value} as an unsigned integer to {@code out}, starting at * {@code offset}. Returns the number of bytes written. */ public static int writeUnsignedLeb128p1(ByteOutput out, int value) { return writeUnsignedLeb128(out, value + 1); } /** * Writes {@code value} as a signed integer to {@code out}, starting at * {@code offset}. Returns the number of bytes written. */ public static int writeSignedLeb128(ByteOutput out, int value) { int remaining = value >> 7; boolean hasMore = true; int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; int bytesWritten = 0; while (hasMore) { hasMore = (remaining != end) || ((remaining & 1) != ((value >> 6) & 1)); out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0))); ++bytesWritten; value = remaining; remaining >>= 7; } return bytesWritten; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/MethodHandle.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; public class MethodHandle extends Item { public enum MethodHandleType { METHOD_HANDLE_TYPE_STATIC_PUT(0x00), METHOD_HANDLE_TYPE_STATIC_GET(0x01), METHOD_HANDLE_TYPE_INSTANCE_PUT(0x02), METHOD_HANDLE_TYPE_INSTANCE_GET(0x03), METHOD_HANDLE_TYPE_INVOKE_STATIC(0x04), METHOD_HANDLE_TYPE_INVOKE_INSTANCE(0x05), METHOD_HANDLE_TYPE_INVOKE_DIRECT(0x06), METHOD_HANDLE_TYPE_INVOKE_CONSTRUCTOR(0x07), METHOD_HANDLE_TYPE_INVOKE_INTERFACE(0x08); public final int value; MethodHandleType(int value) { this.value = value; } public static MethodHandleType fromValue(int value) { for (MethodHandleType methodHandleType : values()) { if (methodHandleType.value == value) { return methodHandleType; } } throw new IllegalArgumentException(String.valueOf(value)); } public boolean isField() { switch (this) { case METHOD_HANDLE_TYPE_STATIC_PUT: case METHOD_HANDLE_TYPE_STATIC_GET: case METHOD_HANDLE_TYPE_INSTANCE_PUT: case METHOD_HANDLE_TYPE_INSTANCE_GET: return true; default: return false; } } } public MethodHandleType methodHandleType; public int unused1; public int fieldOrMethodId; public int unused2; public MethodHandle(int off, MethodHandleType methodHandleType, int unused1, int fieldOrMethodId, int unused2) { super(off); this.methodHandleType = methodHandleType; this.unused1 = unused1; this.fieldOrMethodId = fieldOrMethodId; this.unused2 = unused2; } @Override public int byteCountInDex() { return SizeOf.USHORT * 4; } @Override public int compareTo(MethodHandle o) { if (methodHandleType != o.methodHandleType) { return methodHandleType.compareTo(o.methodHandleType); } return CompareUtils.uCompare(fieldOrMethodId, o.fieldOrMethodId); } public void writeTo(Dex.Section out) { out.writeUnsignedShort(methodHandleType.value); out.writeUnsignedShort(unused1); out.writeUnsignedShort(fieldOrMethodId); out.writeUnsignedShort(unused2); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/MethodId.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dex.util.HashCodeHelper; public final class MethodId extends Item { public int declaringClassIndex; public int protoIndex; public int nameIndex; public MethodId(int off, int declaringClassIndex, int protoIndex, int nameIndex) { super(off); this.declaringClassIndex = declaringClassIndex; this.protoIndex = protoIndex; this.nameIndex = nameIndex; } public int compareTo(MethodId other) { if (declaringClassIndex != other.declaringClassIndex) { return CompareUtils.uCompare(declaringClassIndex, other.declaringClassIndex); } if (nameIndex != other.nameIndex) { return CompareUtils.uCompare(nameIndex, other.nameIndex); } return CompareUtils.uCompare(protoIndex, other.protoIndex); } @Override public int hashCode() { return HashCodeHelper.hash(declaringClassIndex, protoIndex, nameIndex); } @Override public boolean equals(Object obj) { if (!(obj instanceof MethodId)) { return false; } return this.compareTo((MethodId) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.MEMBER_ID_ITEM; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/Mutf8.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.util.ByteInput; import java.io.UTFDataFormatException; /** * Modified UTF-8 as described in the dex file format spec. * *

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

{ public static final int UNDEF_INDEX = -1; public static final int UNDEF_OFFSET = -1; public final short type; public boolean isElementFourByteAligned; public int size = 0; public int off = UNDEF_OFFSET; public int byteCount = 0; public Section(int type, boolean isElementFourByteAligned) { this.type = (short) type; this.isElementFourByteAligned = isElementFourByteAligned; if (type == SECTION_TYPE_HEADER) { off = 0; size = 1; byteCount = SizeOf.HEADER_ITEM; } else if (type == SECTION_TYPE_MAPLIST) { size = 1; } } public boolean exists() { return size > 0; } private int remapTypeOrderId(int type) { switch (type) { case SECTION_TYPE_HEADER: { return 0; } case SECTION_TYPE_STRINGIDS: { return 1; } case SECTION_TYPE_TYPEIDS: { return 2; } case SECTION_TYPE_PROTOIDS: { return 3; } case SECTION_TYPE_FIELDIDS: { return 4; } case SECTION_TYPE_METHODIDS: { return 5; } case SECTION_TYPE_METHODHANDLES: { return 6; } case SECTION_TYPE_CLASSDEFS: { return 7; } case SECTION_TYPE_STRINGDATAS: { return 8; } case SECTION_TYPE_TYPELISTS: { return 9; } case SECTION_TYPE_ANNOTATIONS: { return 10; } case SECTION_TYPE_ANNOTATIONSETS: { return 11; } case SECTION_TYPE_ANNOTATIONSETREFLISTS: { return 12; } case SECTION_TYPE_ANNOTATIONSDIRECTORIES: { return 13; } case SECTION_TYPE_DEBUGINFOS: { return 14; } case SECTION_TYPE_CODES: { return 15; } case SECTION_TYPE_CLASSDATA: { return 16; } case SECTION_TYPE_ENCODEDARRAYS: { return 17; } case SECTION_TYPE_CALLSITEIDS: { return 18; } case SECTION_TYPE_MAPLIST: { return 19; } default: { throw new IllegalArgumentException("unknown section type: " + type); } } } public int compareTo(Section section) { if (off != section.off) { return off < section.off ? -1 : 1; } int remappedType = remapTypeOrderId(type); int otherRemappedType = remapTypeOrderId(section.type); if (remappedType != otherRemappedType) { return (remappedType < otherRemappedType ? -1 : 1); } return 0; } @Override public String toString() { return String.format("Section[type=%#x,off=%#x,size=%#x,byteCount=%#x]", type, off, size, byteCount); } public static abstract class Item implements Comparable { public int off; public Item(int off) { this.off = off; } @Override public int hashCode() { return super.hashCode(); } @Override @SuppressWarnings("unchecked") public boolean equals(Object obj) { return compareTo((T) obj) == 0; } public abstract int byteCountInDex(); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TypeList.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.util.CompareUtils; import java.util.Arrays; public final class TypeList extends Item { public static final TypeList EMPTY = new TypeList(0, Dex.EMPTY_SHORT_ARRAY); public short[] types; public TypeList(int off, short[] types) { super(off); this.types = types; } @Override public int compareTo(TypeList other) { return CompareUtils.uArrCompare(types, other.types); } @Override public int hashCode() { return Arrays.hashCode(types); } @Override public boolean equals(Object obj) { if (!(obj instanceof TypeList)) { return false; } return this.compareTo((TypeList) obj) == 0; } @Override public int byteCountInDex() { return SizeOf.UINT + types.length * SizeOf.USHORT; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex.io; import com.tencent.tinker.android.dex.Annotation; import com.tencent.tinker.android.dex.AnnotationSet; import com.tencent.tinker.android.dex.AnnotationSetRefList; import com.tencent.tinker.android.dex.AnnotationsDirectory; import com.tencent.tinker.android.dex.CallSiteId; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.DebugInfoItem; import com.tencent.tinker.android.dex.DexException; import com.tencent.tinker.android.dex.EncodedValue; import com.tencent.tinker.android.dex.EncodedValueReader; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.Leb128; import com.tencent.tinker.android.dex.MethodHandle; import com.tencent.tinker.android.dex.MethodHandle.MethodHandleType; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.Mutf8; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.StringData; import com.tencent.tinker.android.dex.TypeList; import com.tencent.tinker.android.dex.util.ByteInput; import com.tencent.tinker.android.dex.util.ByteOutput; import java.io.ByteArrayOutputStream; import java.io.UTFDataFormatException; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * *** This file is NOT a part of AOSP. *** * Created by tangyinsheng on 2016/6/30. */ public class DexDataBuffer implements ByteInput, ByteOutput { public static final int DEFAULT_BUFFER_SIZE = 512; private static final short[] EMPTY_SHORT_ARRAY = new short[0]; private static final Code.Try[] EMPTY_TRY_ARRAY = new Code.Try[0]; private static final Code.CatchHandler[] EMPTY_CATCHHANDLER_ARRAY = new Code.CatchHandler[0]; private ByteBuffer data; private int dataBound; private boolean isResizeAllowed; public DexDataBuffer() { this.data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); this.data.order(ByteOrder.LITTLE_ENDIAN); this.dataBound = this.data.position(); this.data.limit(this.data.capacity()); this.isResizeAllowed = true; } public DexDataBuffer(ByteBuffer data) { this.data = data; this.data.order(ByteOrder.LITTLE_ENDIAN); this.dataBound = data.limit(); this.isResizeAllowed = false; } public DexDataBuffer(ByteBuffer data, boolean isResizeAllowed) { this.data = data; this.data.order(ByteOrder.LITTLE_ENDIAN); this.dataBound = data.limit(); this.isResizeAllowed = isResizeAllowed; } public int position() { return data.position(); } public void position(int pos) { data.position(pos); } public int available() { return dataBound - data.position(); } private void ensureBufferSize(int bytes) { if (this.data.position() + bytes > this.data.limit()) { if (this.isResizeAllowed) { byte[] array = this.data.array(); byte[] newArray = new byte[array.length + bytes + (array.length >> 1)]; System.arraycopy(array, 0, newArray, 0, this.data.position()); int lastPos = this.data.position(); this.data = ByteBuffer.wrap(newArray); this.data.order(ByteOrder.LITTLE_ENDIAN); this.data.position(lastPos); this.data.limit(this.data.capacity()); } } } public byte[] array() { byte[] result = new byte[this.dataBound]; byte[] dataArray = this.data.array(); System.arraycopy(dataArray, 0, result, 0, this.dataBound); return result; } @Override public byte readByte() { return data.get(); } public int readUnsignedByte() { return readByte() & 0xFF; } public short readShort() { return data.getShort(); } public int readUnsignedShort() { return readShort() & 0xffff; } public int readInt() { return data.getInt(); } public byte[] readByteArray(int length) { byte[] result = new byte[length]; data.get(result); return result; } public short[] readShortArray(int length) { if (length == 0) { return EMPTY_SHORT_ARRAY; } short[] result = new short[length]; for (int i = 0; i < length; i++) { result[i] = readShort(); } return result; } public int readUleb128() { return Leb128.readUnsignedLeb128(this); } public int readUleb128p1() { return Leb128.readUnsignedLeb128(this) - 1; } public int readSleb128() { return Leb128.readSignedLeb128(this); } public StringData readStringData() { int off = data.position(); try { int expectedLength = readUleb128(); String result = Mutf8.decode(this, new char[expectedLength]); if (result.length() != expectedLength) { throw new DexException("Declared length " + expectedLength + " doesn't match decoded length of " + result.length()); } return new StringData(off, result); } catch (UTFDataFormatException e) { throw new DexException(e); } } public TypeList readTypeList() { int off = data.position(); int size = readInt(); short[] types = readShortArray(size); return new TypeList(off, types); } public FieldId readFieldId() { int off = data.position(); int declaringClassIndex = readUnsignedShort(); int typeIndex = readUnsignedShort(); int nameIndex = readInt(); return new FieldId(off, declaringClassIndex, typeIndex, nameIndex); } public MethodId readMethodId() { int off = data.position(); int declaringClassIndex = readUnsignedShort(); int protoIndex = readUnsignedShort(); int nameIndex = readInt(); return new MethodId(off, declaringClassIndex, protoIndex, nameIndex); } public ProtoId readProtoId() { int off = data.position(); int shortyIndex = readInt(); int returnTypeIndex = readInt(); int parametersOffset = readInt(); return new ProtoId(off, shortyIndex, returnTypeIndex, parametersOffset); } public CallSiteId readCallSiteId() { int off = data.position(); int callsiteOffset = readInt(); return new CallSiteId(off, callsiteOffset); } public MethodHandle readMethodHandle() { int off = data.position(); MethodHandleType methodHandleType = MethodHandleType.fromValue(readUnsignedShort()); int unused1 = readUnsignedShort(); int fieldOrMethodId = readUnsignedShort(); int unused2 = readUnsignedShort(); return new MethodHandle(off, methodHandleType, unused1, fieldOrMethodId, unused2); } public ClassDef readClassDef() { int off = position(); int type = readInt(); int accessFlags = readInt(); int supertype = readInt(); int interfacesOffset = readInt(); int sourceFileIndex = readInt(); int annotationsOffset = readInt(); int classDataOffset = readInt(); int staticValuesOffset = readInt(); return new ClassDef(off, type, accessFlags, supertype, interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, staticValuesOffset); } public Code readCode() { int off = data.position(); int registersSize = readUnsignedShort(); int insSize = readUnsignedShort(); int outsSize = readUnsignedShort(); int triesSize = readUnsignedShort(); int debugInfoOffset = readInt(); int instructionsSize = readInt(); short[] instructions = readShortArray(instructionsSize); Code.Try[] tries; Code.CatchHandler[] catchHandlers; if (triesSize > 0) { if ((instructions.length & 1) == 1) { skip(2); // padding } /* * We can't read the tries until we've read the catch handlers. * Unfortunately they're in the opposite order in the dex file * so we need to read them out-of-order. */ int posBeforeTries = data.position(); skip(triesSize * SizeOf.TRY_ITEM); catchHandlers = readCatchHandlers(); int posAfterCatchHandlers = data.position(); data.position(posBeforeTries); tries = readTries(triesSize, catchHandlers); data.position(posAfterCatchHandlers); } else { tries = EMPTY_TRY_ARRAY; catchHandlers = EMPTY_CATCHHANDLER_ARRAY; } return new Code(off, registersSize, insSize, outsSize, debugInfoOffset, instructions, tries, catchHandlers); } private Code.CatchHandler[] readCatchHandlers() { int baseOffset = data.position(); int catchHandlersSize = readUleb128(); Code.CatchHandler[] result = new Code.CatchHandler[catchHandlersSize]; for (int i = 0; i < catchHandlersSize; i++) { int offset = data.position() - baseOffset; result[i] = readCatchHandler(offset); } return result; } private Code.Try[] readTries(int triesSize, Code.CatchHandler[] catchHandlers) { Code.Try[] result = new Code.Try[triesSize]; for (int i = 0; i < triesSize; i++) { int startAddress = readInt(); int instructionCount = readUnsignedShort(); int handlerOffset = readUnsignedShort(); int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); result[i] = new Code.Try(startAddress, instructionCount, catchHandlerIndex); } return result; } private int findCatchHandlerIndex(Code.CatchHandler[] catchHandlers, int offset) { for (int i = 0; i < catchHandlers.length; i++) { Code.CatchHandler catchHandler = catchHandlers[i]; if (catchHandler.offset == offset) { return i; } } throw new IllegalArgumentException(); } private Code.CatchHandler readCatchHandler(int offset) { int size = readSleb128(); int handlersCount = Math.abs(size); int[] typeIndexes = new int[handlersCount]; int[] addresses = new int[handlersCount]; for (int i = 0; i < handlersCount; i++) { typeIndexes[i] = readUleb128(); addresses[i] = readUleb128(); } int catchAllAddress = size <= 0 ? readUleb128() : -1; return new Code.CatchHandler(typeIndexes, addresses, catchAllAddress, offset); } public DebugInfoItem readDebugInfoItem() { int off = data.position(); int lineStart = readUleb128(); int parametersSize = readUleb128(); int[] parameterNames = new int[parametersSize]; for (int i = 0; i < parametersSize; ++i) { parameterNames[i] = readUleb128p1(); } ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream(64); final ByteArrayOutputStream baosRef = baos; ByteOutput outAdapter = new ByteOutput() { @Override public void writeByte(int i) { baosRef.write(i); } }; outside_whileloop: while (true) { int opcode = readByte(); baos.write(opcode); switch (opcode) { case DebugInfoItem.DBG_END_SEQUENCE: { break outside_whileloop; } case DebugInfoItem.DBG_ADVANCE_PC: { int addrDiff = readUleb128(); Leb128.writeUnsignedLeb128(outAdapter, addrDiff); break; } case DebugInfoItem.DBG_ADVANCE_LINE: { int lineDiff = readSleb128(); Leb128.writeSignedLeb128(outAdapter, lineDiff); break; } case DebugInfoItem.DBG_START_LOCAL: case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { int registerNum = readUleb128(); Leb128.writeUnsignedLeb128(outAdapter, registerNum); int nameIndex = readUleb128p1(); Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); int typeIndex = readUleb128p1(); Leb128.writeUnsignedLeb128p1(outAdapter, typeIndex); if (opcode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { int sigIndex = readUleb128p1(); Leb128.writeUnsignedLeb128p1(outAdapter, sigIndex); } break; } case DebugInfoItem.DBG_END_LOCAL: case DebugInfoItem.DBG_RESTART_LOCAL: { int registerNum = readUleb128(); Leb128.writeUnsignedLeb128(outAdapter, registerNum); break; } case DebugInfoItem.DBG_SET_FILE: { int nameIndex = readUleb128p1(); Leb128.writeUnsignedLeb128p1(outAdapter, nameIndex); break; } case DebugInfoItem.DBG_SET_PROLOGUE_END: case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: default: { break; } } } byte[] infoSTM = baos.toByteArray(); return new DebugInfoItem(off, lineStart, parameterNames, infoSTM); } finally { if (baos != null) { try { baos.close(); } catch (Exception e) { // Do nothing. } } } } public ClassData readClassData() { int off = data.position(); int staticFieldsSize = readUleb128(); int instanceFieldsSize = readUleb128(); int directMethodsSize = readUleb128(); int virtualMethodsSize = readUleb128(); ClassData.Field[] staticFields = readFields(staticFieldsSize); ClassData.Field[] instanceFields = readFields(instanceFieldsSize); ClassData.Method[] directMethods = readMethods(directMethodsSize); ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); return new ClassData(off, staticFields, instanceFields, directMethods, virtualMethods); } private ClassData.Field[] readFields(int count) { ClassData.Field[] result = new ClassData.Field[count]; int fieldIndex = 0; for (int i = 0; i < count; i++) { fieldIndex += readUleb128(); // field index diff int accessFlags = readUleb128(); result[i] = new ClassData.Field(fieldIndex, accessFlags); } return result; } private ClassData.Method[] readMethods(int count) { ClassData.Method[] result = new ClassData.Method[count]; int methodIndex = 0; for (int i = 0; i < count; i++) { methodIndex += readUleb128(); // method index diff int accessFlags = readUleb128(); int codeOff = readUleb128(); result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); } return result; } /** * Returns a byte array containing the bytes from {@code start} to this * section's current position. */ private byte[] getBytesFrom(int start) { int end = data.position(); byte[] result = new byte[end - start]; data.position(start); data.get(result); return result; } public Annotation readAnnotation() { int off = data.position(); byte visibility = readByte(); int start = data.position(); new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue(); return new Annotation(off, visibility, new EncodedValue(start, getBytesFrom(start))); } public AnnotationSet readAnnotationSet() { int off = data.position(); int size = readInt(); int[] annotationOffsets = new int[size]; for (int i = 0; i < size; ++i) { annotationOffsets[i] = readInt(); } return new AnnotationSet(off, annotationOffsets); } public AnnotationSetRefList readAnnotationSetRefList() { int off = data.position(); int size = readInt(); int[] annotationSetRefItems = new int[size]; for (int i = 0; i < size; ++i) { annotationSetRefItems[i] = readInt(); } return new AnnotationSetRefList(off, annotationSetRefItems); } public AnnotationsDirectory readAnnotationsDirectory() { int off = data.position(); int classAnnotationsOffset = readInt(); int fieldsSize = readInt(); int methodsSize = readInt(); int parameterListSize = readInt(); int[][] fieldAnnotations = new int[fieldsSize][2]; for (int i = 0; i < fieldsSize; ++i) { // field index fieldAnnotations[i][0] = readInt(); // annotations offset fieldAnnotations[i][1] = readInt(); } int[][] methodAnnotations = new int[methodsSize][2]; for (int i = 0; i < methodsSize; ++i) { // method index methodAnnotations[i][0] = readInt(); // annotation set offset methodAnnotations[i][1] = readInt(); } int[][] parameterAnnotations = new int[parameterListSize][2]; for (int i = 0; i < parameterListSize; ++i) { // method index parameterAnnotations[i][0] = readInt(); // annotations offset parameterAnnotations[i][1] = readInt(); } return new AnnotationsDirectory(off, classAnnotationsOffset, fieldAnnotations, methodAnnotations, parameterAnnotations); } public EncodedValue readEncodedArray() { int start = data.position(); new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); return new EncodedValue(start, getBytesFrom(start)); } public void skip(int count) { if (count < 0) { throw new IllegalArgumentException(); } data.position(data.position() + count); } public void skipWithAutoExpand(int count) { ensureBufferSize(SizeOf.UBYTE * count); skip(count); } /** * Skips bytes until the position is aligned to a multiple of 4. */ public void alignToFourBytes() { data.position((data.position() + 3) & ~3); } /** * Writes 0x00 until the position is aligned to a multiple of 4. */ public void alignToFourBytesWithZeroFill() { int alignedPos = SizeOf.roundToTimesOfFour(data.position()); ensureBufferSize((alignedPos - data.position()) * SizeOf.UBYTE); while ((data.position() & 3) != 0) { data.put((byte) 0); } if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } @Override public void writeByte(int b) { ensureBufferSize(SizeOf.UBYTE); data.put((byte) b); if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } public void writeShort(short i) { ensureBufferSize(SizeOf.USHORT); data.putShort(i); if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } public void writeUnsignedShort(int i) { short s = (short) i; if (i != (s & 0xffff)) { throw new IllegalArgumentException("Expected an unsigned short: " + i); } writeShort(s); } public void writeInt(int i) { ensureBufferSize(SizeOf.UINT); this.data.putInt(i); if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } public void write(byte[] bytes) { ensureBufferSize(bytes.length * SizeOf.UBYTE); this.data.put(bytes); if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } public void write(short[] shorts) { ensureBufferSize(shorts.length * SizeOf.USHORT); for (short s : shorts) { writeShort(s); } if (this.data.position() > this.dataBound) { this.dataBound = this.data.position(); } } public void writeUleb128(int i) { Leb128.writeUnsignedLeb128(this, i); } public void writeUleb128p1(int i) { writeUleb128(i + 1); } public void writeSleb128(int i) { Leb128.writeSignedLeb128(this, i); } /** * Write String data into current section. * * @return real offset of item we've just written in this section. */ public int writeStringData(StringData stringData) { int off = data.position(); try { int length = stringData.value.length(); writeUleb128(length); write(Mutf8.encode(stringData.value)); writeByte(0); return off; } catch (UTFDataFormatException e) { throw new AssertionError(e); } } /** * Write TypeList item into current section. * * @return real offset of item we've just written in this section. */ public int writeTypeList(TypeList typeList) { int off = data.position(); short[] types = typeList.types; writeInt(types.length); for (short type : types) { writeShort(type); } return off; } /** * Write FieldId item into current section. * * @return real offset of item we've just written in this section. */ public int writeFieldId(FieldId fieldId) { int off = data.position(); writeUnsignedShort(fieldId.declaringClassIndex); writeUnsignedShort(fieldId.typeIndex); writeInt(fieldId.nameIndex); return off; } /** * Write MethodId item into current section. * * @return real offset of item we've just written in this section. */ public int writeMethodId(MethodId methodId) { int off = data.position(); writeUnsignedShort(methodId.declaringClassIndex); writeUnsignedShort(methodId.protoIndex); writeInt(methodId.nameIndex); return off; } /** * Write ProtoId item into current section. * * @return real offset of item we've just written in this section. */ public int writeProtoId(ProtoId protoId) { int off = data.position(); writeInt(protoId.shortyIndex); writeInt(protoId.returnTypeIndex); writeInt(protoId.parametersOffset); return off; } /** * Write CallSiteId item into current section. * * @return real offset of item we've just written in this section. */ public int writeCallSiteId(CallSiteId callSiteId) { int off = data.position(); writeInt(callSiteId.offset); return off; } /** * Write MethodHandle item into current section. * * @return real offset of item we've just written in this section. */ public int writeMethodHandle(MethodHandle methodHandle) { int off = data.position(); writeUnsignedShort(methodHandle.methodHandleType.value); writeUnsignedShort(methodHandle.unused1); writeUnsignedShort(methodHandle.fieldOrMethodId); writeUnsignedShort(methodHandle.unused2); return off; } /** * Write ClassDef item into current section. * * @return real offset of item we've just written in this section. */ public int writeClassDef(ClassDef classDef) { int off = data.position(); writeInt(classDef.typeIndex); writeInt(classDef.accessFlags); writeInt(classDef.supertypeIndex); writeInt(classDef.interfacesOffset); writeInt(classDef.sourceFileIndex); writeInt(classDef.annotationsOffset); writeInt(classDef.classDataOffset); writeInt(classDef.staticValuesOffset); return off; } /** * Write Code item into current section. * * @return real offset of item we've just written in this section. */ public int writeCode(Code code) { int off = data.position(); writeUnsignedShort(code.registersSize); writeUnsignedShort(code.insSize); writeUnsignedShort(code.outsSize); writeUnsignedShort(code.tries.length); writeInt(code.debugInfoOffset); writeInt(code.instructions.length); write(code.instructions); if (code.tries.length > 0) { if ((code.instructions.length & 1) == 1) { writeShort((short) 0); // padding } /* * We can't write the tries until we've written the catch handlers. * Unfortunately they're in the opposite order in the dex file so we * need to transform them out-of-order. */ int posBeforeTries = data.position(); skipWithAutoExpand(code.tries.length * SizeOf.TRY_ITEM); int[] offsets = writeCatchHandlers(code.catchHandlers); int posAfterCatchHandlers = data.position(); data.position(posBeforeTries); writeTries(code.tries, offsets); data.position(posAfterCatchHandlers); } return off; } private int[] writeCatchHandlers(Code.CatchHandler[] catchHandlers) { int baseOffset = data.position(); writeUleb128(catchHandlers.length); int[] offsets = new int[catchHandlers.length]; for (int i = 0; i < catchHandlers.length; i++) { offsets[i] = data.position() - baseOffset; writeCatchHandler(catchHandlers[i]); } return offsets; } private void writeCatchHandler(Code.CatchHandler catchHandler) { int catchAllAddress = catchHandler.catchAllAddress; int[] typeIndexes = catchHandler.typeIndexes; int[] addresses = catchHandler.addresses; if (catchAllAddress != -1) { writeSleb128(-typeIndexes.length); } else { writeSleb128(typeIndexes.length); } for (int i = 0; i < typeIndexes.length; i++) { writeUleb128(typeIndexes[i]); writeUleb128(addresses[i]); } if (catchAllAddress != -1) { writeUleb128(catchAllAddress); } } private void writeTries(Code.Try[] tries, int[] catchHandlerOffsets) { for (Code.Try tryItem : tries) { writeInt(tryItem.startAddress); writeUnsignedShort(tryItem.instructionCount); writeUnsignedShort(catchHandlerOffsets[tryItem.catchHandlerIndex]); } } /** * Write DebugInfo item into current section. * * @return real offset of item we've just written in this section. */ public int writeDebugInfoItem(DebugInfoItem debugInfoItem) { int off = data.position(); writeUleb128(debugInfoItem.lineStart); int parametersSize = debugInfoItem.parameterNames.length; writeUleb128(parametersSize); for (int i = 0; i < parametersSize; ++i) { int parameterName = debugInfoItem.parameterNames[i]; writeUleb128p1(parameterName); } write(debugInfoItem.infoSTM); return off; } /** * Write ClassData item into current section. * * @return real offset of item we've just written in this section. */ public int writeClassData(ClassData classData) { int off = data.position(); writeUleb128(classData.staticFields.length); writeUleb128(classData.instanceFields.length); writeUleb128(classData.directMethods.length); writeUleb128(classData.virtualMethods.length); writeFields(classData.staticFields); writeFields(classData.instanceFields); writeMethods(classData.directMethods); writeMethods(classData.virtualMethods); return off; } private void writeFields(ClassData.Field[] fields) { int lastOutFieldIndex = 0; for (ClassData.Field field : fields) { writeUleb128(field.fieldIndex - lastOutFieldIndex); lastOutFieldIndex = field.fieldIndex; writeUleb128(field.accessFlags); } } private void writeMethods(ClassData.Method[] methods) { int lastOutMethodIndex = 0; for (ClassData.Method method : methods) { writeUleb128(method.methodIndex - lastOutMethodIndex); lastOutMethodIndex = method.methodIndex; writeUleb128(method.accessFlags); writeUleb128(method.codeOffset); } } /** * Write Annotation item into current section. * * @return real offset of item we've just written in this section. */ public int writeAnnotation(Annotation annotation) { int off = data.position(); writeByte(annotation.visibility); writeEncodedArray(annotation.encodedAnnotation); return off; } /** * Write AnnotationSet item into current section. * * @return real offset of item we've just written in this section. */ public int writeAnnotationSet(AnnotationSet annotationSet) { int off = data.position(); writeInt(annotationSet.annotationOffsets.length); for (int annotationOffset : annotationSet.annotationOffsets) { writeInt(annotationOffset); } return off; } /** * Write AnnotationSetRefList item into current section. * * @return real offset of item we've just written in this section. */ public int writeAnnotationSetRefList(AnnotationSetRefList annotationSetRefList) { int off = data.position(); writeInt(annotationSetRefList.annotationSetRefItems.length); for (int annotationSetRefItem : annotationSetRefList.annotationSetRefItems) { writeInt(annotationSetRefItem); } return off; } /** * Write AnnotationDirectory item into current section. * * @return real offset of item we've just written in this section. */ public int writeAnnotationsDirectory(AnnotationsDirectory annotationsDirectory) { int off = data.position(); writeInt(annotationsDirectory.classAnnotationsOffset); writeInt(annotationsDirectory.fieldAnnotations.length); writeInt(annotationsDirectory.methodAnnotations.length); writeInt(annotationsDirectory.parameterAnnotations.length); for (int[] fieldAnnotation : annotationsDirectory.fieldAnnotations) { writeInt(fieldAnnotation[0]); writeInt(fieldAnnotation[1]); } for (int[] methodAnnotation : annotationsDirectory.methodAnnotations) { writeInt(methodAnnotation[0]); writeInt(methodAnnotation[1]); } for (int[] parameterAnnotation : annotationsDirectory.parameterAnnotations) { writeInt(parameterAnnotation[0]); writeInt(parameterAnnotation[1]); } return off; } /** * Write EncodedValue/EncodedArray item into current section. * * @return real offset of item we've just written in this section. */ public int writeEncodedArray(EncodedValue encodedValue) { int off = data.position(); write(encodedValue.data); return off; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteInput.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex.util; /** * A byte source. */ public interface ByteInput { /** * Returns a byte. * * @throws IndexOutOfBoundsException if all bytes have been read. */ byte readByte(); } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/ByteOutput.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex.util; /** * A byte sink. */ public interface ByteOutput { /** * Writes a byte. * * @throws IndexOutOfBoundsException if all bytes have been written. */ void writeByte(int i); } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/CompareUtils.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex.util; import java.util.Comparator; /** * *** This file is NOT a part of AOSP. *** * Created by tangyinsheng on 2016/6/28. */ public final class CompareUtils { private CompareUtils() { } public static int uCompare(byte ubyteA, byte ubyteB) { if (ubyteA == ubyteB) { return 0; } int a = ubyteA & 0xFF; int b = ubyteB & 0xFF; return a < b ? -1 : 1; } public static int uCompare(short ushortA, short ushortB) { if (ushortA == ushortB) { return 0; } int a = ushortA & 0xFFFF; int b = ushortB & 0xFFFF; return a < b ? -1 : 1; } public static int uCompare(int uintA, int uintB) { if (uintA == uintB) { return 0; } long a = uintA & 0xFFFFFFFFL; long b = uintB & 0xFFFFFFFFL; return a < b ? -1 : 1; } public static int uArrCompare(byte[] ubyteArrA, byte[] ubyteArrB) { int lenA = ubyteArrA.length; int lenB = ubyteArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = uCompare(ubyteArrA[i], ubyteArrB[i]); if (res != 0) { return res; } } return 0; } } public static int uArrCompare(short[] ushortArrA, short[] ushortArrB) { int lenA = ushortArrA.length; int lenB = ushortArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = uCompare(ushortArrA[i], ushortArrB[i]); if (res != 0) { return res; } } return 0; } } public static int uArrCompare(int[] uintArrA, int[] uintArrB) { int lenA = uintArrA.length; int lenB = uintArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = uCompare(uintArrA[i], uintArrB[i]); if (res != 0) { return res; } } return 0; } } public static int sCompare(byte sbyteA, byte sbyteB) { if (sbyteA == sbyteB) { return 0; } return sbyteA < sbyteB ? -1 : 1; } public static int sCompare(short sshortA, short sshortB) { if (sshortA == sshortB) { return 0; } return sshortA < sshortB ? -1 : 1; } public static int sCompare(int sintA, int sintB) { if (sintA == sintB) { return 0; } return sintA < sintB ? -1 : 1; } public static int sCompare(long slongA, long slongB) { if (slongA == slongB) { return 0; } return slongA < slongB ? -1 : 1; } public static int sArrCompare(byte[] sbyteArrA, byte[] sbyteArrB) { int lenA = sbyteArrA.length; int lenB = sbyteArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = sCompare(sbyteArrA[i], sbyteArrB[i]); if (res != 0) { return res; } } return 0; } } public static int sArrCompare(short[] sshortArrA, short[] sshortArrB) { int lenA = sshortArrA.length; int lenB = sshortArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = sCompare(sshortArrA[i], sshortArrB[i]); if (res != 0) { return res; } } return 0; } } public static int sArrCompare(int[] sintArrA, int[] sintArrB) { int lenA = sintArrA.length; int lenB = sintArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = sCompare(sintArrA[i], sintArrB[i]); if (res != 0) { return res; } } return 0; } } public static int sArrCompare(long[] slongArrA, long[] slongArrB) { int lenA = slongArrA.length; int lenB = slongArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = sCompare(slongArrA[i], slongArrB[i]); if (res != 0) { return res; } } return 0; } } public static > int aArrCompare(T[] aArrA, T[] aArrB) { int lenA = aArrA.length; int lenB = aArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = aArrA[i].compareTo(aArrB[i]); if (res != 0) { return res; } } return 0; } } public static int aArrCompare(T[] aArrA, T[] aArrB, Comparator cmptor) { int lenA = aArrA.length; int lenB = aArrB.length; if (lenA < lenB) { return -1; } else if (lenA > lenB) { return 1; } else { for (int i = 0; i < lenA; ++i) { int res = cmptor.compare(aArrA[i], aArrB[i]); if (res != 0) { return res; } } return 0; } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/FileUtils.java ================================================ /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dex.util; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * *** This file is NOT a part of AOSP. *** * File I/O utilities. */ public final class FileUtils { private FileUtils() { } /** * Reads the named file, translating {@link IOException} to a * {@link RuntimeException} of some sort. * * @param fileName {@code non-null;} name of the file to read * @return {@code non-null;} contents of the file */ public static byte[] readFile(String fileName) throws IOException { File file = new File(fileName); return readFile(file); } /** * Reads the given file, translating {@link IOException} to a * {@link RuntimeException} of some sort. * * @param file {@code non-null;} the file to read * @return {@code non-null;} contents of the file * @throws IOException */ public static byte[] readFile(File file) throws IOException { if (!file.exists()) { throw new RuntimeException(file + ": file not found"); } if (!file.isFile()) { throw new RuntimeException(file + ": not a file"); } if (!file.canRead()) { throw new RuntimeException(file + ": file not readable"); } long longLength = file.length(); int length = (int) longLength; if (length != longLength) { throw new RuntimeException(file + ": file too long"); } ByteArrayOutputStream baos = new ByteArrayOutputStream(length); InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); byte[] buffer = new byte[8192]; int bytesRead = 0; while ((bytesRead = in.read(buffer)) > 0) { baos.write(buffer, 0, bytesRead); } } finally { if (in != null) { try { in.close(); } catch (Exception e) { // ignored. } } } return baos.toByteArray(); } public static byte[] readStream(InputStream is) throws IOException { return readStream(is, 32 * 1024); } public static byte[] readStream(InputStream is, int initSize) throws IOException { if (initSize <= 0) { initSize = 32 * 1024; } ByteArrayOutputStream baos = new ByteArrayOutputStream(initSize); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) > 0) { baos.write(buffer, 0, bytesRead); } return baos.toByteArray(); } /** * Returns true if {@code fileName} names a .zip, .jar, or .apk. */ public static boolean hasArchiveSuffix(String fileName) { return fileName.endsWith(".zip") || fileName.endsWith(".jar") || fileName.endsWith(".apk"); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/util/HashCodeHelper.java ================================================ package com.tencent.tinker.android.dex.util; import java.lang.reflect.Array; import java.util.Arrays; public final class HashCodeHelper { public static int hash(Object... values) { if (values == null || values.length == 0) { return 0; } int result = 0; for (Object v : values) { if (v == null) { continue; } if (v instanceof Number) { result += v.hashCode(); } else if (v instanceof boolean[]) { result += Arrays.hashCode((boolean[]) v); } else if (v instanceof byte[]) { result += Arrays.hashCode((byte[]) v); } else if (v instanceof char[]) { result += Arrays.hashCode((char[]) v); } else if (v instanceof short[]) { result += Arrays.hashCode((short[]) v); } else if (v instanceof int[]) { result += Arrays.hashCode((int[]) v); } else if (v instanceof long[]) { result += Arrays.hashCode((long[]) v); } else if (v instanceof float[]) { result += Arrays.hashCode((float[]) v); } else if (v instanceof double[]) { result += Arrays.hashCode((double[]) v); } else if (v instanceof Object[]) { result += Arrays.hashCode((Object[]) v); } else if (v.getClass().isArray()) { for (int i = 0; i < Array.getLength(v); ++i) { result += hash(Array.get(v, i)); } } else { result += v.hashCode(); } } return result; } private HashCodeHelper() { throw new UnsupportedOperationException(); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/CodeCursor.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import com.tencent.tinker.android.utils.SparseIntArray; /** * Cursor over code units, for reading or writing out Dalvik bytecode. */ public abstract class CodeCursor { /** base address map */ private final SparseIntArray baseAddressMap; /** next index within {@link #array} to read from or write to */ private int cursor; /** * Constructs an instance. */ public CodeCursor() { this.baseAddressMap = new SparseIntArray(); this.cursor = 0; } /** * Gets the cursor. The cursor is the offset in code units from * the start of the input of the next code unit to be read or * written, where the input generally consists of the code for a * single method. */ public final int cursor() { return cursor; } /** * Gets the base address associated with the current cursor. This * differs from the cursor value when explicitly set (by {@link * #setBaseAddress}). This is used, in particular, to convey base * addresses to switch data payload instructions, whose relative * addresses are relative to the address of a dependant switch * instruction. */ public final int baseAddressForCursor() { int index = baseAddressMap.indexOfKey(cursor); if (index < 0) { return cursor; } else { return baseAddressMap.valueAt(index); } } /** * Sets the base address for the given target address to be as indicated. * * @see #baseAddressForCursor */ public final void setBaseAddress(int targetAddress, int baseAddress) { baseAddressMap.put(targetAddress, baseAddress); } /** * Reset this cursor's status. */ public void reset() { this.baseAddressMap.clear(); this.cursor = 0; } /** * Advance the cursor by the indicated amount. */ protected final void advance(int amount) { cursor += amount; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionCodec.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import static com.tencent.tinker.android.dx.instruction.Opcodes.extractOpcodeFromUnit; import com.tencent.tinker.android.dex.DexException; import com.tencent.tinker.android.dx.util.Hex; import java.io.EOFException; import java.util.Arrays; /** * Encode/Decode instruction opcode. */ public final class InstructionCodec { /** * "Unknown." Used for undefined opcodes. */ public static final int INDEX_TYPE_UNKNOWN = 0; /** * no index used */ public static final int INDEX_TYPE_NONE = 1; /** * type reference index */ public static final int INDEX_TYPE_TYPE_REF = 2; /** * string reference index */ public static final int INDEX_TYPE_STRING_REF = 3; /** * method reference index */ public static final int INDEX_TYPE_METHOD_REF = 4; /** * field reference index */ public static final int INDEX_TYPE_FIELD_REF = 5; /** * method index and a proto index */ public static final int INDEX_TYPE_METHOD_AND_PROTO_REF = 6; /** * call site reference index */ public static final int INDEX_TYPE_CALL_SITE_REF = 7; /** * method handle reference index (for loading constant method handles) */ public static final int INDEX_TYPE_METHOD_HANDLE_REF = 8; /** * proto reference index (for loading constant proto ref) */ public static final int INDEX_TYPE_PROTO_REF = 9; /** * "Unknown." Used for undefined opcodes. */ public static final int INSN_FORMAT_UNKNOWN = 0; public static final int INSN_FORMAT_00X = 1; public static final int INSN_FORMAT_10T = 2; public static final int INSN_FORMAT_10X = 3; public static final int INSN_FORMAT_11N = 4; public static final int INSN_FORMAT_11X = 5; public static final int INSN_FORMAT_12X = 6; public static final int INSN_FORMAT_20T = 7; public static final int INSN_FORMAT_21C = 8; public static final int INSN_FORMAT_21H = 9; public static final int INSN_FORMAT_21S = 10; public static final int INSN_FORMAT_21T = 11; public static final int INSN_FORMAT_22B = 12; public static final int INSN_FORMAT_22C = 13; public static final int INSN_FORMAT_22S = 14; public static final int INSN_FORMAT_22T = 15; public static final int INSN_FORMAT_22X = 16; public static final int INSN_FORMAT_23X = 17; public static final int INSN_FORMAT_30T = 18; public static final int INSN_FORMAT_31C = 19; public static final int INSN_FORMAT_31I = 20; public static final int INSN_FORMAT_31T = 21; public static final int INSN_FORMAT_32X = 22; public static final int INSN_FORMAT_35C = 23; public static final int INSN_FORMAT_3RC = 24; public static final int INSN_FORMAT_51L = 25; public static final int INSN_FORMAT_45CC = 26; public static final int INSN_FORMAT_4RCC = 27; public static final int INSN_FORMAT_PACKED_SWITCH_PAYLOAD = 28; public static final int INSN_FORMAT_SPARSE_SWITCH_PAYLOAD = 29; public static final int INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD = 30; private InstructionCodec() { throw new UnsupportedOperationException(); } public static void decode(ShortArrayCodeInput in, InstructionVisitor iv) throws EOFException { in.reset(); while (in.hasMore()) { final int currentAddress = in.cursor(); final int opcodeUnit = in.read(); final int opcode = extractOpcodeFromUnit(opcodeUnit); final int insnFormat = getInstructionFormat(opcode); switch (insnFormat) { case INSN_FORMAT_00X: { iv.visitZeroRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, 0L); break; } case INSN_FORMAT_10X: { final int literal = byte1(opcodeUnit); iv.visitZeroRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal); break; } case INSN_FORMAT_12X: { final int a = nibble2(opcodeUnit); final int b = nibble3(opcodeUnit); iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, 0L, a, b); break; } case INSN_FORMAT_11N: { final int a = nibble2(opcodeUnit); final int literal = (nibble3(opcodeUnit) << 28) >> 28; // sign-extend iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a); break; } case INSN_FORMAT_11X: { final int a = byte1(opcodeUnit); iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, 0L, a); break; } case INSN_FORMAT_10T: { final int target = currentAddress + (byte) byte1(opcodeUnit); // sign-extend iv.visitZeroRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, target, 0L); break; } case INSN_FORMAT_20T: { final int literal = byte1(opcodeUnit); // should be zero final int target = currentAddress + (short) in.read(); // sign-extend iv.visitZeroRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, target, literal); break; } case INSN_FORMAT_22X: { final int a = byte1(opcodeUnit); final int b = in.read(); iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, 0L, a, b); break; } case INSN_FORMAT_21T: { final int a = byte1(opcodeUnit); final int target = currentAddress + (short) in.read(); // sign-extend iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, target, 0L, a); break; } case INSN_FORMAT_21S: { final int a = byte1(opcodeUnit); final int literal = (short) in.read(); // sign-extend iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a); break; } case INSN_FORMAT_21H: { final int a = byte1(opcodeUnit); long literal = (short) in.read(); // sign-extend /* * Format 21h decodes differently depending on the opcode, * because the "signed hat" might represent either a 32- * or 64- bit value. */ literal <<= (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a); break; } case INSN_FORMAT_21C: { final int a = byte1(opcodeUnit); final int index = in.read(); final int indexType = getInstructionIndexType(opcode); iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); break; } case INSN_FORMAT_23X: { final int a = byte1(opcodeUnit); final int bc = in.read(); final int b = byte0(bc); final int c = byte1(bc); iv.visitThreeRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, 0L, a, b, c); break; } case INSN_FORMAT_22B: { final int a = byte1(opcodeUnit); final int bc = in.read(); final int b = byte0(bc); final int literal = (byte) byte1(bc); // sign-extend iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a, b); break; } case INSN_FORMAT_22T: { final int a = nibble2(opcodeUnit); final int b = nibble3(opcodeUnit); final int target = currentAddress + (short) in.read(); // sign-extend iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, target, 0L, a, b); break; } case INSN_FORMAT_22S: { final int a = nibble2(opcodeUnit); final int b = nibble3(opcodeUnit); final int literal = (short) in.read(); // sign-extend iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a, b); break; } case INSN_FORMAT_22C: { final int a = nibble2(opcodeUnit); final int b = nibble3(opcodeUnit); final int index = in.read(); final int indexType = getInstructionIndexType(opcode); iv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b); break; } case INSN_FORMAT_30T: { final int literal = byte1(opcodeUnit); // should be zero final int target = currentAddress + in.readInt(); iv.visitZeroRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, target, literal); break; } case INSN_FORMAT_32X: { final int literal = byte1(opcodeUnit); // should be zero final int a = in.read(); final int b = in.read(); iv.visitTwoRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a, b); break; } case INSN_FORMAT_31I: { final int a = byte1(opcodeUnit); final int literal = in.readInt(); iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a); break; } case INSN_FORMAT_31T: { int a = InstructionCodec.byte1(opcodeUnit); int target = currentAddress + in.readInt(); /* * Switch instructions need to "forward" their addresses to their * payload target instructions. */ switch (opcode) { case Opcodes.PACKED_SWITCH: case Opcodes.SPARSE_SWITCH: { // plus 1 means when we actually lookup the currentAddress // by (payload insn address + 1), in.setBaseAddress(target + 1, currentAddress); break; } default: { break; } } iv.visitOneRegisterInsn(currentAddress, opcode, 0, InstructionCodec.INDEX_TYPE_NONE, target, 0L, a); break; } case INSN_FORMAT_31C: { final int a = byte1(opcodeUnit); final int index = in.readInt(); final int indexType = getInstructionIndexType(opcode); iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); break; } case INSN_FORMAT_35C: { final int e = nibble2(opcodeUnit); final int registerCount = nibble3(opcodeUnit); final int index = in.read(); final int abcd = in.read(); final int a = nibble0(abcd); final int b = nibble1(abcd); final int c = nibble2(abcd); final int d = nibble3(abcd); final int indexType = getInstructionIndexType(opcode); switch (registerCount) { case 0: iv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L); break; case 1: iv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a); break; case 2: iv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b); break; case 3: iv.visitThreeRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c); break; case 4: iv.visitFourRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d); break; case 5: iv.visitFiveRegisterInsn(currentAddress, opcode, index, indexType, 0, 0L, a, b, c, d, e); break; default: throw new DexException("bogus registerCount: " + Hex.uNibble(registerCount)); // FIXME debug here. } break; } case INSN_FORMAT_3RC: { final int registerCount = byte1(opcodeUnit); final int index = in.read(); final int a = in.read(); final int indexType = getInstructionIndexType(opcode); iv.visitRegisterRangeInsn(currentAddress, opcode, index, indexType, 0, 0L, a, registerCount); break; } case INSN_FORMAT_51L: { final int a = byte1(opcodeUnit); final long literal = in.readLong(); iv.visitOneRegisterInsn(currentAddress, opcode, 0, INDEX_TYPE_NONE, 0, literal, a); break; } case INSN_FORMAT_45CC: { if (opcode != Opcodes.INVOKE_POLYMORPHIC) { // 45cc isn't currently used for anything other than invoke-polymorphic. // If that changes, add a more general DecodedInstruction for this format. // TODO keep track on aosp dx project if such changes happen. throw new UnsupportedOperationException(String.valueOf(opcode)); } final int g = nibble2(opcodeUnit); final int registerCount = nibble3(opcodeUnit); final int methodIndex = in.read(); final int cdef = in.read(); final int c = nibble0(cdef); final int d = nibble1(cdef); final int e = nibble2(cdef); final int f = nibble3(cdef); final int protoIndex = in.read(); final int indexType = getInstructionIndexType(opcode); if (registerCount < 1 || registerCount > 5) { throw new DexException("bogus registerCount: " + Hex.uNibble(registerCount)); } final int[] registers = Arrays.copyOfRange(new int[] {c, d, e, f, g}, 0, registerCount); iv.visitInvokePolymorphicInstruction(currentAddress, opcode, methodIndex, indexType, protoIndex, registers); break; } case INSN_FORMAT_4RCC: { if (opcode != Opcodes.INVOKE_POLYMORPHIC_RANGE) { // 4rcc isn't currently used for anything other than invoke-polymorphic. // If that changes, add a more general DecodedInstruction for this format. // TODO keep track on aosp dx project if such changes happen. throw new UnsupportedOperationException(String.valueOf(opcode)); } final int registerCount = byte1(opcodeUnit); final int methodIndex = in.read(); final int c = in.read(); final int protoIndex = in.read(); final int indexType = getInstructionIndexType(opcode); iv.visitInvokePolymorphicRangeInstruction(currentAddress, opcode, methodIndex, indexType, c, registerCount, protoIndex); break; } case INSN_FORMAT_PACKED_SWITCH_PAYLOAD: { final int baseAddress = in.baseAddressForCursor(); final int size = in.read(); final int firstKey = in.readInt(); final int[] targets = new int[size]; for (int i = 0; i < size; i++) { targets[i] = baseAddress + in.readInt(); } iv.visitPackedSwitchPayloadInsn(currentAddress, opcodeUnit, firstKey, targets); break; } case INSN_FORMAT_SPARSE_SWITCH_PAYLOAD: { final int baseAddress = in.baseAddressForCursor(); final int size = in.read(); final int[] keys = new int[size]; final int[] targets = new int[size]; for (int i = 0; i < size; i++) { keys[i] = in.readInt(); } for (int i = 0; i < size; i++) { targets[i] = baseAddress + in.readInt(); } iv.visitSparseSwitchPayloadInsn(currentAddress, opcodeUnit, keys, targets); break; } case INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD: { final int elementWidth = in.read(); final int size = in.readInt(); switch (elementWidth) { case 1: { byte[] array = new byte[size]; boolean even = true; for (int i = 0, value = 0; i < size; ++i, even = !even) { if (even) { value = in.read(); } array[i] = (byte) (value & 0xff); value >>= 8; } iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 1); break; } case 2: { short[] array = new short[size]; for (int i = 0; i < size; i++) { array[i] = (short) in.read(); } iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 2); break; } case 4: { int[] array = new int[size]; for (int i = 0; i < size; i++) { array[i] = in.readInt(); } iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 4); break; } case 8: { long[] array = new long[size]; for (int i = 0; i < size; i++) { array[i] = in.readLong(); } iv.visitFillArrayDataPayloadInsn(currentAddress, opcodeUnit, array, array.length, 8); break; } default: { throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); } } break; } default: throw new DexException("Unknown instruction format: " + insnFormat); } } } public static void encode(ShortArrayCodeOutput out, InstructionWriter iw) { final int opcode = iw.currOpcode; final int insnFormat = getInstructionFormat(opcode); switch (insnFormat) { case INSN_FORMAT_00X: case INSN_FORMAT_10X: { out.write((short) opcode); break; } case INSN_FORMAT_12X: { out.write(codeUnit(opcode, makeByte(iw.currRegA, iw.currRegB))); break; } case INSN_FORMAT_11N: { out.write(codeUnit(opcode, makeByte(iw.currRegA, getLiteralNibble(iw.currLiteral)))); break; } case INSN_FORMAT_11X: { out.write(codeUnit(opcode, iw.currRegA)); break; } case INSN_FORMAT_10T: { final int relativeTarget = getTargetByte(iw.currTarget, out.cursor()); out.write(codeUnit(opcode, relativeTarget)); break; } case INSN_FORMAT_20T: { final short relativeTarget = getTargetUnit(iw.currTarget, out.cursor()); out.write((short) opcode, relativeTarget); break; } case INSN_FORMAT_22X: { out.write(codeUnit(opcode, iw.currRegA), getBUnit(iw.currRegB)); break; } case INSN_FORMAT_21T: { final short relativeTarget = getTargetUnit(iw.currTarget, out.cursor()); out.write(codeUnit(opcode, iw.currRegA), relativeTarget); break; } case INSN_FORMAT_21S: { out.write(codeUnit(opcode, iw.currRegA), getLiteralUnit(iw.currLiteral)); break; } case INSN_FORMAT_21H: { final int shift = (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; final short literal = (short) (iw.currLiteral >> shift); out.write(codeUnit(opcode, iw.currRegA), literal); break; } case INSN_FORMAT_21C: { out.write(codeUnit(opcode, iw.currRegA), (short) iw.currIndex); break; } case INSN_FORMAT_23X: { out.write(codeUnit(opcode, iw.currRegA), codeUnit(iw.currRegB, iw.currRegC)); break; } case INSN_FORMAT_22B: { out.write(codeUnit(opcode, iw.currRegA), codeUnit(iw.currRegB, getLiteralByte(iw.currLiteral))); break; } case INSN_FORMAT_22T: { final short relativeTarget = getTargetUnit(iw.currTarget, out.cursor()); out.write(codeUnit(opcode, makeByte(iw.currRegA, iw.currRegB)), relativeTarget); break; } case INSN_FORMAT_22S: { out.write(codeUnit(opcode, makeByte(iw.currRegA, iw.currRegB)), getLiteralUnit(iw.currLiteral)); break; } case INSN_FORMAT_22C: { out.write(codeUnit(opcode, makeByte(iw.currRegA, iw.currRegB)), (short) iw.currIndex); break; } case INSN_FORMAT_30T: { final int relativeTarget = getTarget(iw.currTarget, out.cursor()); out.write((short) opcode, unit0(relativeTarget), unit1(relativeTarget)); break; } case INSN_FORMAT_32X: { out.write((short) opcode, getAUnit(iw.currRegA), getBUnit(iw.currRegB)); break; } case INSN_FORMAT_31I: { final int literal = getLiteralInt(iw.currLiteral); out.write(codeUnit(opcode, iw.currRegA), unit0(literal), unit1(literal)); break; } case INSN_FORMAT_31T: { /* * Switch instructions need to "forward" their addresses to their * payload target instructions. */ switch (opcode) { case Opcodes.PACKED_SWITCH: case Opcodes.SPARSE_SWITCH: { out.setBaseAddress(iw.currTarget, out.cursor()); break; } default: // fall out } final int relativeTarget = getTarget(iw.currTarget, out.cursor()); out.write(codeUnit(opcode, iw.currRegA), unit0(relativeTarget), unit1(relativeTarget)); break; } case INSN_FORMAT_31C: { final int index = iw.currIndex; out.write(codeUnit(opcode, iw.currRegA), unit0(index), unit1(index)); break; } case INSN_FORMAT_35C: { out.write(codeUnit(opcode, makeByte(iw.currRegE, iw.currRegisterCount)), (short) iw.currIndex, codeUnit(iw.currRegA, iw.currRegB, iw.currRegC, iw.currRegD)); break; } case INSN_FORMAT_3RC: { out.write(codeUnit(opcode, iw.currRegisterCount), (short) iw.currIndex, getAUnit(iw.currRegA)); break; } case INSN_FORMAT_51L: { final long literal = iw.currLiteral; out.write(codeUnit(opcode, iw.currRegA), unit0(literal), unit1(literal), unit2(literal), unit3(literal)); break; } case INSN_FORMAT_45CC: { out.write(codeUnit(opcode, makeByte(iw.currRegG, iw.currRegisterCount)), (short) iw.currIndex, codeUnit(iw.currRegC, iw.currRegD, iw.currRegE, iw.currRegF), (short) iw.currProtoIndex); break; } case INSN_FORMAT_4RCC: { out.write(codeUnit(opcode, iw.currRegisterCount), (short) iw.currIndex, getCUnit(iw.currRegC), (short) iw.currProtoIndex); break; } case INSN_FORMAT_PACKED_SWITCH_PAYLOAD: { final int[] targets = iw.currTargets; final int baseAddress = out.baseAddressForCursor(); out.write((short) opcode); out.write(asUnsignedUnit(targets.length)); out.writeInt(iw.currFirstKey); for (int target : targets) { out.writeInt(target - baseAddress); } break; } case INSN_FORMAT_SPARSE_SWITCH_PAYLOAD: { final int[] keys = iw.currKeys; final int[] targets = iw.currTargets; final int baseAddress = out.baseAddressForCursor(); out.write((short) opcode); out.write(asUnsignedUnit(targets.length)); for (int key : keys) { out.writeInt(key); } for (int target : targets) { out.writeInt(target - baseAddress); } break; } case INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD: { final short elementWidth = (short) iw.currElementWidth; out.write((short) opcode); out.write(elementWidth); out.writeInt(iw.currSize); final Object data = iw.currData; switch (elementWidth) { case 1: out.write((byte[]) data); break; case 2: out.write((short[]) data); break; case 4: out.write((int[]) data); break; case 8: out.write((long[]) data); break; default: { throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); } } break; } default: throw new DexException("Unknown instruction format: " + insnFormat); } } public static short codeUnit(int lowByte, int highByte) { if ((lowByte & ~0xff) != 0) { throw new IllegalArgumentException("bogus lowByte"); } if ((highByte & ~0xff) != 0) { throw new IllegalArgumentException("bogus highByte"); } return (short) (lowByte | (highByte << 8)); } public static short codeUnit(int nibble0, int nibble1, int nibble2, int nibble3) { if ((nibble0 & ~0xf) != 0) { throw new IllegalArgumentException("bogus nibble0"); } if ((nibble1 & ~0xf) != 0) { throw new IllegalArgumentException("bogus nibble1"); } if ((nibble2 & ~0xf) != 0) { throw new IllegalArgumentException("bogus nibble2"); } if ((nibble3 & ~0xf) != 0) { throw new IllegalArgumentException("bogus nibble3"); } return (short) (nibble0 | (nibble1 << 4) | (nibble2 << 8) | (nibble3 << 12)); } public static int makeByte(int lowNibble, int highNibble) { if ((lowNibble & ~0xf) != 0) { throw new IllegalArgumentException("bogus lowNibble"); } if ((highNibble & ~0xf) != 0) { throw new IllegalArgumentException("bogus highNibble"); } return lowNibble | (highNibble << 4); } public static short asUnsignedUnit(int value) { if ((value & ~0xffff) != 0) { throw new IllegalArgumentException("bogus unsigned code unit"); } return (short) value; } public static short unit0(int value) { return (short) value; } public static short unit1(int value) { return (short) (value >> 16); } public static short unit0(long value) { return (short) value; } public static short unit1(long value) { return (short) (value >> 16); } public static short unit2(long value) { return (short) (value >> 32); } public static short unit3(long value) { return (short) (value >> 48); } private static int byte0(int value) { return value & 0xff; } private static int byte1(int value) { return (value >> 8) & 0xff; } private static int nibble0(int value) { return value & 0xf; } private static int nibble1(int value) { return (value >> 4) & 0xf; } private static int nibble2(int value) { return (value >> 8) & 0xf; } private static int nibble3(int value) { return (value >> 12) & 0xf; } public static int getTargetByte(int target, int baseAddress) { int relativeTarget = getTarget(target, baseAddress); if (relativeTarget != (byte) relativeTarget) { throw new DexException( "Target out of range: " + Hex.s4(relativeTarget) + ", perhaps you need to enable force jumbo mode." ); } return relativeTarget & 0xff; } public static short getTargetUnit(int target, int baseAddress) { int relativeTarget = getTarget(target, baseAddress); if (relativeTarget != (short) relativeTarget) { throw new DexException( "Target out of range: " + Hex.s4(relativeTarget) + ", perhaps you need to enable force jumbo mode." ); } return (short) relativeTarget; } public static int getTarget(int target, int baseAddress) { return target - baseAddress; } public static int getLiteralByte(long literal) { if (literal != (byte) literal) { throw new DexException("Literal out of range: " + Hex.u8(literal)); } return (int) literal & 0xff; } public static short getLiteralUnit(long literal) { if (literal != (short) literal) { throw new DexException("Literal out of range: " + Hex.u8(literal)); } return (short) literal; } public static int getLiteralInt(long literal) { if (literal != (int) literal) { throw new DexException("Literal out of range: " + Hex.u8(literal)); } return (int) literal; } public static int getLiteralNibble(long literal) { if ((literal < -8) || (literal > 7)) { throw new DexException("Literal out of range: " + Hex.u8(literal)); } return (int) literal & 0xf; } /** * Gets the A register number, as a code unit. This will throw if the * value is out of the range of an unsigned code unit. */ public static short getAUnit(int a) { if ((a & ~0xffff) != 0) { throw new DexException("Register A out of range: " + Hex.u8(a)); } return (short) a; } /** * Gets the B register number, as a code unit. This will throw if the * value is out of the range of an unsigned code unit. */ public static short getBUnit(int b) { if ((b & ~0xffff) != 0) { throw new DexException("Register B out of range: " + Hex.u8(b)); } return (short) b; } /** * Gets the C register number, as a code unit. This will throw if the * value is out of the range of an unsigned code unit. */ public static short getCUnit(int c) { if ((c & ~0xffff) != 0) { throw new DexException("Register C out of range: " + Hex.u8(c)); } return (short) c; } public static int getInstructionIndexType(int opcode) { switch (opcode) { case Opcodes.CONST_STRING: case Opcodes.CONST_STRING_JUMBO: { return INDEX_TYPE_STRING_REF; } case Opcodes.CONST_METHOD_HANDLE: { return INDEX_TYPE_METHOD_HANDLE_REF; } case Opcodes.CONST_METHOD_TYPE: { return INDEX_TYPE_PROTO_REF; } case Opcodes.CONST_CLASS: case Opcodes.CHECK_CAST: case Opcodes.INSTANCE_OF: case Opcodes.NEW_INSTANCE: case Opcodes.NEW_ARRAY: case Opcodes.FILLED_NEW_ARRAY: case Opcodes.FILLED_NEW_ARRAY_RANGE: { return INDEX_TYPE_TYPE_REF; } case Opcodes.IGET: case Opcodes.IGET_WIDE: case Opcodes.IGET_OBJECT: case Opcodes.IGET_BOOLEAN: case Opcodes.IGET_BYTE: case Opcodes.IGET_CHAR: case Opcodes.IGET_SHORT: case Opcodes.IPUT: case Opcodes.IPUT_WIDE: case Opcodes.IPUT_OBJECT: case Opcodes.IPUT_BOOLEAN: case Opcodes.IPUT_BYTE: case Opcodes.IPUT_CHAR: case Opcodes.IPUT_SHORT: case Opcodes.SGET: case Opcodes.SGET_WIDE: case Opcodes.SGET_OBJECT: case Opcodes.SGET_BOOLEAN: case Opcodes.SGET_BYTE: case Opcodes.SGET_CHAR: case Opcodes.SGET_SHORT: case Opcodes.SPUT: case Opcodes.SPUT_WIDE: case Opcodes.SPUT_OBJECT: case Opcodes.SPUT_BOOLEAN: case Opcodes.SPUT_BYTE: case Opcodes.SPUT_CHAR: case Opcodes.SPUT_SHORT: { return INDEX_TYPE_FIELD_REF; } case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: case Opcodes.INVOKE_VIRTUAL_RANGE: case Opcodes.INVOKE_SUPER_RANGE: case Opcodes.INVOKE_DIRECT_RANGE: case Opcodes.INVOKE_STATIC_RANGE: case Opcodes.INVOKE_INTERFACE_RANGE: { return INDEX_TYPE_METHOD_REF; } case Opcodes.INVOKE_POLYMORPHIC: case Opcodes.INVOKE_POLYMORPHIC_RANGE: { return INDEX_TYPE_METHOD_AND_PROTO_REF; } case Opcodes.INVOKE_CUSTOM: case Opcodes.INVOKE_CUSTOM_RANGE: { return INDEX_TYPE_CALL_SITE_REF; } case Opcodes.SPECIAL_FORMAT: case Opcodes.PACKED_SWITCH_PAYLOAD: case Opcodes.SPARSE_SWITCH_PAYLOAD: case Opcodes.FILL_ARRAY_DATA_PAYLOAD: case Opcodes.NOP: case Opcodes.MOVE: case Opcodes.MOVE_FROM16: case Opcodes.MOVE_16: case Opcodes.MOVE_WIDE: case Opcodes.MOVE_WIDE_FROM16: case Opcodes.MOVE_WIDE_16: case Opcodes.MOVE_OBJECT: case Opcodes.MOVE_OBJECT_FROM16: case Opcodes.MOVE_OBJECT_16: case Opcodes.MOVE_RESULT: case Opcodes.MOVE_RESULT_WIDE: case Opcodes.MOVE_RESULT_OBJECT: case Opcodes.MOVE_EXCEPTION: case Opcodes.RETURN_VOID: case Opcodes.RETURN: case Opcodes.RETURN_WIDE: case Opcodes.RETURN_OBJECT: case Opcodes.CONST_4: case Opcodes.CONST_16: case Opcodes.CONST: case Opcodes.CONST_HIGH16: case Opcodes.CONST_WIDE_16: case Opcodes.CONST_WIDE_32: case Opcodes.CONST_WIDE: case Opcodes.CONST_WIDE_HIGH16: case Opcodes.MONITOR_ENTER: case Opcodes.MONITOR_EXIT: case Opcodes.ARRAY_LENGTH: case Opcodes.FILL_ARRAY_DATA: case Opcodes.THROW: case Opcodes.GOTO: case Opcodes.GOTO_16: case Opcodes.GOTO_32: case Opcodes.PACKED_SWITCH: case Opcodes.SPARSE_SWITCH: case Opcodes.CMPL_FLOAT: case Opcodes.CMPG_FLOAT: case Opcodes.CMPL_DOUBLE: case Opcodes.CMPG_DOUBLE: case Opcodes.CMP_LONG: case Opcodes.IF_EQ: case Opcodes.IF_NE: case Opcodes.IF_LT: case Opcodes.IF_GE: case Opcodes.IF_GT: case Opcodes.IF_LE: case Opcodes.IF_EQZ: case Opcodes.IF_NEZ: case Opcodes.IF_LTZ: case Opcodes.IF_GEZ: case Opcodes.IF_GTZ: case Opcodes.IF_LEZ: case Opcodes.AGET: case Opcodes.AGET_WIDE: case Opcodes.AGET_OBJECT: case Opcodes.AGET_BOOLEAN: case Opcodes.AGET_BYTE: case Opcodes.AGET_CHAR: case Opcodes.AGET_SHORT: case Opcodes.APUT: case Opcodes.APUT_WIDE: case Opcodes.APUT_OBJECT: case Opcodes.APUT_BOOLEAN: case Opcodes.APUT_BYTE: case Opcodes.APUT_CHAR: case Opcodes.APUT_SHORT: case Opcodes.NEG_INT: case Opcodes.NOT_INT: case Opcodes.NEG_LONG: case Opcodes.NOT_LONG: case Opcodes.NEG_FLOAT: case Opcodes.NEG_DOUBLE: case Opcodes.INT_TO_LONG: case Opcodes.INT_TO_FLOAT: case Opcodes.INT_TO_DOUBLE: case Opcodes.LONG_TO_INT: case Opcodes.LONG_TO_FLOAT: case Opcodes.LONG_TO_DOUBLE: case Opcodes.FLOAT_TO_INT: case Opcodes.FLOAT_TO_LONG: case Opcodes.FLOAT_TO_DOUBLE: case Opcodes.DOUBLE_TO_INT: case Opcodes.DOUBLE_TO_LONG: case Opcodes.DOUBLE_TO_FLOAT: case Opcodes.INT_TO_BYTE: case Opcodes.INT_TO_CHAR: case Opcodes.INT_TO_SHORT: case Opcodes.ADD_INT: case Opcodes.SUB_INT: case Opcodes.MUL_INT: case Opcodes.DIV_INT: case Opcodes.REM_INT: case Opcodes.AND_INT: case Opcodes.OR_INT: case Opcodes.XOR_INT: case Opcodes.SHL_INT: case Opcodes.SHR_INT: case Opcodes.USHR_INT: case Opcodes.ADD_LONG: case Opcodes.SUB_LONG: case Opcodes.MUL_LONG: case Opcodes.DIV_LONG: case Opcodes.REM_LONG: case Opcodes.AND_LONG: case Opcodes.OR_LONG: case Opcodes.XOR_LONG: case Opcodes.SHL_LONG: case Opcodes.SHR_LONG: case Opcodes.USHR_LONG: case Opcodes.ADD_FLOAT: case Opcodes.SUB_FLOAT: case Opcodes.MUL_FLOAT: case Opcodes.DIV_FLOAT: case Opcodes.REM_FLOAT: case Opcodes.ADD_DOUBLE: case Opcodes.SUB_DOUBLE: case Opcodes.MUL_DOUBLE: case Opcodes.DIV_DOUBLE: case Opcodes.REM_DOUBLE: case Opcodes.ADD_INT_2ADDR: case Opcodes.SUB_INT_2ADDR: case Opcodes.MUL_INT_2ADDR: case Opcodes.DIV_INT_2ADDR: case Opcodes.REM_INT_2ADDR: case Opcodes.AND_INT_2ADDR: case Opcodes.OR_INT_2ADDR: case Opcodes.XOR_INT_2ADDR: case Opcodes.SHL_INT_2ADDR: case Opcodes.SHR_INT_2ADDR: case Opcodes.USHR_INT_2ADDR: case Opcodes.ADD_LONG_2ADDR: case Opcodes.SUB_LONG_2ADDR: case Opcodes.MUL_LONG_2ADDR: case Opcodes.DIV_LONG_2ADDR: case Opcodes.REM_LONG_2ADDR: case Opcodes.AND_LONG_2ADDR: case Opcodes.OR_LONG_2ADDR: case Opcodes.XOR_LONG_2ADDR: case Opcodes.SHL_LONG_2ADDR: case Opcodes.SHR_LONG_2ADDR: case Opcodes.USHR_LONG_2ADDR: case Opcodes.ADD_FLOAT_2ADDR: case Opcodes.SUB_FLOAT_2ADDR: case Opcodes.MUL_FLOAT_2ADDR: case Opcodes.DIV_FLOAT_2ADDR: case Opcodes.REM_FLOAT_2ADDR: case Opcodes.ADD_DOUBLE_2ADDR: case Opcodes.SUB_DOUBLE_2ADDR: case Opcodes.MUL_DOUBLE_2ADDR: case Opcodes.DIV_DOUBLE_2ADDR: case Opcodes.REM_DOUBLE_2ADDR: case Opcodes.ADD_INT_LIT16: case Opcodes.RSUB_INT: case Opcodes.MUL_INT_LIT16: case Opcodes.DIV_INT_LIT16: case Opcodes.REM_INT_LIT16: case Opcodes.AND_INT_LIT16: case Opcodes.OR_INT_LIT16: case Opcodes.XOR_INT_LIT16: case Opcodes.ADD_INT_LIT8: case Opcodes.RSUB_INT_LIT8: case Opcodes.MUL_INT_LIT8: case Opcodes.DIV_INT_LIT8: case Opcodes.REM_INT_LIT8: case Opcodes.AND_INT_LIT8: case Opcodes.OR_INT_LIT8: case Opcodes.XOR_INT_LIT8: case Opcodes.SHL_INT_LIT8: case Opcodes.SHR_INT_LIT8: case Opcodes.USHR_INT_LIT8: { return INDEX_TYPE_NONE; } default: { return INDEX_TYPE_UNKNOWN; } } } public static int getInstructionFormat(int opcode) { switch (opcode) { case Opcodes.SPECIAL_FORMAT: { return INSN_FORMAT_00X; } case Opcodes.NOP: case Opcodes.RETURN_VOID: { return INSN_FORMAT_10X; } case Opcodes.MOVE: case Opcodes.MOVE_WIDE: case Opcodes.MOVE_OBJECT: case Opcodes.ARRAY_LENGTH: case Opcodes.NEG_INT: case Opcodes.NOT_INT: case Opcodes.NEG_LONG: case Opcodes.NOT_LONG: case Opcodes.NEG_FLOAT: case Opcodes.NEG_DOUBLE: case Opcodes.INT_TO_LONG: case Opcodes.INT_TO_FLOAT: case Opcodes.INT_TO_DOUBLE: case Opcodes.LONG_TO_INT: case Opcodes.LONG_TO_FLOAT: case Opcodes.LONG_TO_DOUBLE: case Opcodes.FLOAT_TO_INT: case Opcodes.FLOAT_TO_LONG: case Opcodes.FLOAT_TO_DOUBLE: case Opcodes.DOUBLE_TO_INT: case Opcodes.DOUBLE_TO_LONG: case Opcodes.DOUBLE_TO_FLOAT: case Opcodes.INT_TO_BYTE: case Opcodes.INT_TO_CHAR: case Opcodes.INT_TO_SHORT: case Opcodes.ADD_INT_2ADDR: case Opcodes.SUB_INT_2ADDR: case Opcodes.MUL_INT_2ADDR: case Opcodes.DIV_INT_2ADDR: case Opcodes.REM_INT_2ADDR: case Opcodes.AND_INT_2ADDR: case Opcodes.OR_INT_2ADDR: case Opcodes.XOR_INT_2ADDR: case Opcodes.SHL_INT_2ADDR: case Opcodes.SHR_INT_2ADDR: case Opcodes.USHR_INT_2ADDR: case Opcodes.ADD_LONG_2ADDR: case Opcodes.SUB_LONG_2ADDR: case Opcodes.MUL_LONG_2ADDR: case Opcodes.DIV_LONG_2ADDR: case Opcodes.REM_LONG_2ADDR: case Opcodes.AND_LONG_2ADDR: case Opcodes.OR_LONG_2ADDR: case Opcodes.XOR_LONG_2ADDR: case Opcodes.SHL_LONG_2ADDR: case Opcodes.SHR_LONG_2ADDR: case Opcodes.USHR_LONG_2ADDR: case Opcodes.ADD_FLOAT_2ADDR: case Opcodes.SUB_FLOAT_2ADDR: case Opcodes.MUL_FLOAT_2ADDR: case Opcodes.DIV_FLOAT_2ADDR: case Opcodes.REM_FLOAT_2ADDR: case Opcodes.ADD_DOUBLE_2ADDR: case Opcodes.SUB_DOUBLE_2ADDR: case Opcodes.MUL_DOUBLE_2ADDR: case Opcodes.DIV_DOUBLE_2ADDR: case Opcodes.REM_DOUBLE_2ADDR: { return INSN_FORMAT_12X; } case Opcodes.MOVE_FROM16: case Opcodes.MOVE_WIDE_FROM16: case Opcodes.MOVE_OBJECT_FROM16: { return INSN_FORMAT_22X; } case Opcodes.MOVE_16: case Opcodes.MOVE_WIDE_16: case Opcodes.MOVE_OBJECT_16: { return INSN_FORMAT_32X; } case Opcodes.MOVE_RESULT: case Opcodes.MOVE_RESULT_WIDE: case Opcodes.MOVE_RESULT_OBJECT: case Opcodes.MOVE_EXCEPTION: case Opcodes.RETURN: case Opcodes.RETURN_WIDE: case Opcodes.RETURN_OBJECT: case Opcodes.MONITOR_ENTER: case Opcodes.MONITOR_EXIT: case Opcodes.THROW: { return INSN_FORMAT_11X; } case Opcodes.CONST_4: { return INSN_FORMAT_11N; } case Opcodes.CONST_16: case Opcodes.CONST_WIDE_16: { return INSN_FORMAT_21S; } case Opcodes.CONST: case Opcodes.CONST_WIDE_32: { return INSN_FORMAT_31I; } case Opcodes.CONST_HIGH16: case Opcodes.CONST_WIDE_HIGH16: { return INSN_FORMAT_21H; } case Opcodes.CONST_WIDE: { return INSN_FORMAT_51L; } case Opcodes.CONST_STRING: case Opcodes.CONST_CLASS: case Opcodes.CHECK_CAST: case Opcodes.NEW_INSTANCE: case Opcodes.SGET: case Opcodes.SGET_WIDE: case Opcodes.SGET_OBJECT: case Opcodes.SGET_BOOLEAN: case Opcodes.SGET_BYTE: case Opcodes.SGET_CHAR: case Opcodes.SGET_SHORT: case Opcodes.SPUT: case Opcodes.SPUT_WIDE: case Opcodes.SPUT_OBJECT: case Opcodes.SPUT_BOOLEAN: case Opcodes.SPUT_BYTE: case Opcodes.SPUT_CHAR: case Opcodes.SPUT_SHORT: case Opcodes.CONST_METHOD_HANDLE: case Opcodes.CONST_METHOD_TYPE: { return INSN_FORMAT_21C; } case Opcodes.CONST_STRING_JUMBO: { return INSN_FORMAT_31C; } case Opcodes.INSTANCE_OF: case Opcodes.NEW_ARRAY: case Opcodes.IGET: case Opcodes.IGET_WIDE: case Opcodes.IGET_OBJECT: case Opcodes.IGET_BOOLEAN: case Opcodes.IGET_BYTE: case Opcodes.IGET_CHAR: case Opcodes.IGET_SHORT: case Opcodes.IPUT: case Opcodes.IPUT_WIDE: case Opcodes.IPUT_OBJECT: case Opcodes.IPUT_BOOLEAN: case Opcodes.IPUT_BYTE: case Opcodes.IPUT_CHAR: case Opcodes.IPUT_SHORT: { return INSN_FORMAT_22C; } case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: case Opcodes.INVOKE_CUSTOM: { return INSN_FORMAT_35C; } case Opcodes.FILLED_NEW_ARRAY_RANGE: case Opcodes.INVOKE_VIRTUAL_RANGE: case Opcodes.INVOKE_SUPER_RANGE: case Opcodes.INVOKE_DIRECT_RANGE: case Opcodes.INVOKE_STATIC_RANGE: case Opcodes.INVOKE_INTERFACE_RANGE: case Opcodes.INVOKE_CUSTOM_RANGE: { return INSN_FORMAT_3RC; } case Opcodes.FILL_ARRAY_DATA: case Opcodes.PACKED_SWITCH: case Opcodes.SPARSE_SWITCH: { return INSN_FORMAT_31T; } case Opcodes.GOTO: { return INSN_FORMAT_10T; } case Opcodes.GOTO_16: { return INSN_FORMAT_20T; } case Opcodes.GOTO_32: { return INSN_FORMAT_30T; } case Opcodes.CMPL_FLOAT: case Opcodes.CMPG_FLOAT: case Opcodes.CMPL_DOUBLE: case Opcodes.CMPG_DOUBLE: case Opcodes.CMP_LONG: case Opcodes.AGET: case Opcodes.AGET_WIDE: case Opcodes.AGET_OBJECT: case Opcodes.AGET_BOOLEAN: case Opcodes.AGET_BYTE: case Opcodes.AGET_CHAR: case Opcodes.AGET_SHORT: case Opcodes.APUT: case Opcodes.APUT_WIDE: case Opcodes.APUT_OBJECT: case Opcodes.APUT_BOOLEAN: case Opcodes.APUT_BYTE: case Opcodes.APUT_CHAR: case Opcodes.APUT_SHORT: case Opcodes.ADD_INT: case Opcodes.SUB_INT: case Opcodes.MUL_INT: case Opcodes.DIV_INT: case Opcodes.REM_INT: case Opcodes.AND_INT: case Opcodes.OR_INT: case Opcodes.XOR_INT: case Opcodes.SHL_INT: case Opcodes.SHR_INT: case Opcodes.USHR_INT: case Opcodes.ADD_LONG: case Opcodes.SUB_LONG: case Opcodes.MUL_LONG: case Opcodes.DIV_LONG: case Opcodes.REM_LONG: case Opcodes.AND_LONG: case Opcodes.OR_LONG: case Opcodes.XOR_LONG: case Opcodes.SHL_LONG: case Opcodes.SHR_LONG: case Opcodes.USHR_LONG: case Opcodes.ADD_FLOAT: case Opcodes.SUB_FLOAT: case Opcodes.MUL_FLOAT: case Opcodes.DIV_FLOAT: case Opcodes.REM_FLOAT: case Opcodes.ADD_DOUBLE: case Opcodes.SUB_DOUBLE: case Opcodes.MUL_DOUBLE: case Opcodes.DIV_DOUBLE: case Opcodes.REM_DOUBLE: { return INSN_FORMAT_23X; } case Opcodes.IF_EQ: case Opcodes.IF_NE: case Opcodes.IF_LT: case Opcodes.IF_GE: case Opcodes.IF_GT: case Opcodes.IF_LE: { return INSN_FORMAT_22T; } case Opcodes.IF_EQZ: case Opcodes.IF_NEZ: case Opcodes.IF_LTZ: case Opcodes.IF_GEZ: case Opcodes.IF_GTZ: case Opcodes.IF_LEZ: { return INSN_FORMAT_21T; } case Opcodes.ADD_INT_LIT16: case Opcodes.RSUB_INT: case Opcodes.MUL_INT_LIT16: case Opcodes.DIV_INT_LIT16: case Opcodes.REM_INT_LIT16: case Opcodes.AND_INT_LIT16: case Opcodes.OR_INT_LIT16: case Opcodes.XOR_INT_LIT16: { return INSN_FORMAT_22S; } case Opcodes.ADD_INT_LIT8: case Opcodes.RSUB_INT_LIT8: case Opcodes.MUL_INT_LIT8: case Opcodes.DIV_INT_LIT8: case Opcodes.REM_INT_LIT8: case Opcodes.AND_INT_LIT8: case Opcodes.OR_INT_LIT8: case Opcodes.XOR_INT_LIT8: case Opcodes.SHL_INT_LIT8: case Opcodes.SHR_INT_LIT8: case Opcodes.USHR_INT_LIT8: { return INSN_FORMAT_22B; } case Opcodes.INVOKE_POLYMORPHIC: { return INSN_FORMAT_45CC; } case Opcodes.INVOKE_POLYMORPHIC_RANGE: { return INSN_FORMAT_4RCC; } case Opcodes.PACKED_SWITCH_PAYLOAD: { return INSN_FORMAT_PACKED_SWITCH_PAYLOAD; } case Opcodes.SPARSE_SWITCH_PAYLOAD: { return INSN_FORMAT_SPARSE_SWITCH_PAYLOAD; } case Opcodes.FILL_ARRAY_DATA_PAYLOAD: { return INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD; } default: { return INSN_FORMAT_UNKNOWN; } } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionComparator.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import com.tencent.tinker.android.dex.DexException; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.android.dx.util.Hex; import java.io.EOFException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * *** This file is NOT a part of AOSP. *** * * Created by tangyinsheng on 2016/7/12. */ public abstract class InstructionComparator { private final InstructionHolder[] insnHolders1; private final InstructionHolder[] insnHolders2; private final Set visitedInsnAddrPairs; private final short[] insns1; private final short[] insns2; public InstructionComparator(short[] insns1, short[] insns2) { this.insns1 = insns1; this.insns2 = insns2; if (insns1 != null) { ShortArrayCodeInput codeIn1 = new ShortArrayCodeInput(insns1); this.insnHolders1 = readInstructionsIntoHolders(codeIn1, insns1.length); } else { this.insnHolders1 = null; } if (insns2 != null) { ShortArrayCodeInput codeIn2 = new ShortArrayCodeInput(insns2); this.insnHolders2 = readInstructionsIntoHolders(codeIn2, insns2.length); } else { this.insnHolders2 = null; } visitedInsnAddrPairs = new HashSet<>(); } private InstructionHolder[] readInstructionsIntoHolders(ShortArrayCodeInput in, int length) { in.reset(); final InstructionHolder[] result = new InstructionHolder[length]; InstructionReader ir = new InstructionReader(in); try { ir.accept(new InstructionVisitor(null) { public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { if (opcode != Opcodes.NOP) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; result[currentAddress] = insnHolder; } } public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = 1; insnHolder.a = a; result[currentAddress] = insnHolder; } public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = 2; insnHolder.a = a; insnHolder.b = b; result[currentAddress] = insnHolder; } public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = 3; insnHolder.a = a; insnHolder.b = b; insnHolder.c = c; result[currentAddress] = insnHolder; } public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = 4; insnHolder.a = a; insnHolder.b = b; insnHolder.c = c; insnHolder.d = d; result[currentAddress] = insnHolder; } public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = 5; insnHolder.a = a; insnHolder.b = b; insnHolder.c = c; insnHolder.d = d; insnHolder.e = e; result[currentAddress] = insnHolder; } public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = index; insnHolder.target = target; insnHolder.literal = literal; insnHolder.registerCount = registerCount; insnHolder.a = a; result[currentAddress] = insnHolder; } @Override public void visitInvokePolymorphicInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int protoIndex, int[] registers) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = methodIndex; insnHolder.registerCount = registers.length; insnHolder.protoIndex = protoIndex; insnHolder.registers = registers; result[currentAddress] = insnHolder; } @Override public void visitInvokePolymorphicRangeInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int c, int registerCount, int protoIndex) { InstructionHolder insnHolder = new InstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.index = methodIndex; insnHolder.c = c; insnHolder.registerCount = registerCount; insnHolder.protoIndex = protoIndex; result[currentAddress] = insnHolder; } public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { SparseSwitchPayloadInsntructionHolder insnHolder = new SparseSwitchPayloadInsntructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.keys = keys; insnHolder.targets = targets; result[currentAddress] = insnHolder; } public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { PackedSwitchPayloadInsntructionHolder insnHolder = new PackedSwitchPayloadInsntructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.firstKey = firstKey; insnHolder.targets = targets; result[currentAddress] = insnHolder; } public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { FillArrayDataPayloadInstructionHolder insnHolder = new FillArrayDataPayloadInstructionHolder(); insnHolder.insnFormat = InstructionCodec.getInstructionFormat(opcode); insnHolder.address = currentAddress; insnHolder.opcode = opcode; insnHolder.data = data; insnHolder.size = size; insnHolder.elementWidth = elementWidth; result[currentAddress] = insnHolder; } }); } catch (EOFException e) { throw new RuntimeException(e); } return result; } public final boolean compare() { try { if (this.insnHolders1 == null && this.insnHolders2 == null) { return true; } if (this.insnHolders1 == null || this.insnHolders2 == null) { return false; } int currAddress1 = 0; int currAddress2 = 0; int insnHolderCount1 = 0; int insnHolderCount2 = 0; while (currAddress1 < insnHolders1.length && currAddress2 < insnHolders2.length) { InstructionHolder insnHolder1 = null; InstructionHolder insnHolder2 = null; while (currAddress1 < insnHolders1.length && insnHolder1 == null) { insnHolder1 = insnHolders1[currAddress1++]; } if (insnHolder1 != null) { ++insnHolderCount1; } else { break; } while (currAddress2 < insnHolders2.length && insnHolder2 == null) { insnHolder2 = insnHolders2[currAddress2++]; } if (insnHolder2 != null) { ++insnHolderCount2; } else { break; } if (!isSameInstruction(insnHolder1, insnHolder2)) { return false; } } while (currAddress1 < insnHolders1.length) { if (insnHolders1[currAddress1++] != null) { return false; } } while (currAddress2 < insnHolders2.length) { if (insnHolders2[currAddress2++] != null) { return false; } } return insnHolderCount1 == insnHolderCount2; } finally { this.visitedInsnAddrPairs.clear(); } } private int getPromotedOpCodeOnDemand(InstructionHolder insn) { final int opcode = insn.opcode; if (opcode == Opcodes.CONST_STRING || opcode == Opcodes.CONST_STRING_JUMBO) { return Opcodes.CONST_STRING_JUMBO; } else if (opcode == Opcodes.GOTO || opcode == Opcodes.GOTO_16 || opcode == Opcodes.GOTO_32) { return Opcodes.GOTO_32; } return opcode; } public boolean isSameInstruction(int insnAddress1, int insnAddress2) { InstructionHolder insnHolder1 = insnAddress1 < this.insnHolders1.length ? this.insnHolders1[insnAddress1] : null; InstructionHolder insnHolder2 = insnAddress2 < this.insnHolders2.length ? this.insnHolders2[insnAddress2] : null; return isSameInstruction(insnHolder1, insnHolder2); } public boolean isSameInstruction(InstructionHolder insnHolder1, InstructionHolder insnHolder2) { if (insnHolder1 == null && insnHolder2 == null) { return true; } if (insnHolder1 == null || insnHolder2 == null) { return false; } if (getPromotedOpCodeOnDemand(insnHolder1) != getPromotedOpCodeOnDemand(insnHolder2)) { return false; } int opcode = insnHolder1.opcode; int insnFormat = insnHolder1.insnFormat; switch (insnFormat) { case InstructionCodec.INSN_FORMAT_10T: case InstructionCodec.INSN_FORMAT_20T: case InstructionCodec.INSN_FORMAT_21T: case InstructionCodec.INSN_FORMAT_22T: case InstructionCodec.INSN_FORMAT_30T: case InstructionCodec.INSN_FORMAT_31T: { final String addrPairStr = insnHolder1.address + "-" + insnHolder2.address; if (this.visitedInsnAddrPairs.add(addrPairStr)) { // If we haven't compared target insns, following the control flow // and do further compare. return isSameInstruction(insnHolder1.target, insnHolder2.target); } else { // If we have already compared target insns, here we can return // true directly. return true; } } case InstructionCodec.INSN_FORMAT_21C: case InstructionCodec.INSN_FORMAT_22C: case InstructionCodec.INSN_FORMAT_31C: case InstructionCodec.INSN_FORMAT_35C: case InstructionCodec.INSN_FORMAT_3RC: { if (!compareIndex(opcode, insnHolder1.index, insnHolder2.index)) { return false; } if (insnHolder1.a != insnHolder2.a) { return false; } if (insnHolder1.b != insnHolder2.b) { return false; } if (insnHolder1.c != insnHolder2.c) { return false; } return (insnHolder1.d == insnHolder2.d); } case InstructionCodec.INSN_FORMAT_45CC: { if (!compareMethod(insnHolder1.index, insnHolder2.index)) { return false; } if (!compareProto(insnHolder1.protoIndex, insnHolder2.protoIndex)) { return false; } return Arrays.equals(insnHolder1.registers, insnHolder2.registers); } case InstructionCodec.INSN_FORMAT_4RCC: { if (!compareMethod(insnHolder1.index, insnHolder2.index)) { return false; } if (!compareProto(insnHolder1.protoIndex, insnHolder2.protoIndex)) { return false; } return insnHolder1.c == insnHolder2.c; } case InstructionCodec.INSN_FORMAT_PACKED_SWITCH_PAYLOAD: { PackedSwitchPayloadInsntructionHolder specInsnHolder1 = (PackedSwitchPayloadInsntructionHolder) insnHolder1; PackedSwitchPayloadInsntructionHolder specInsnHolder2 = (PackedSwitchPayloadInsntructionHolder) insnHolder2; if (specInsnHolder1.firstKey != specInsnHolder2.firstKey) { return false; } if (specInsnHolder1.targets.length != specInsnHolder2.targets.length) { return false; } int targetCount = specInsnHolder1.targets.length; for (int i = 0; i < targetCount; ++i) { if (!isSameInstruction(specInsnHolder1.targets[i], specInsnHolder2.targets[i])) { return false; } } return true; } case InstructionCodec.INSN_FORMAT_SPARSE_SWITCH_PAYLOAD: { SparseSwitchPayloadInsntructionHolder specInsnHolder1 = (SparseSwitchPayloadInsntructionHolder) insnHolder1; SparseSwitchPayloadInsntructionHolder specInsnHolder2 = (SparseSwitchPayloadInsntructionHolder) insnHolder2; if (CompareUtils.uArrCompare(specInsnHolder1.keys, specInsnHolder2.keys) != 0) { return false; } if (specInsnHolder1.targets.length != specInsnHolder2.targets.length) { return false; } int targetCount = specInsnHolder1.targets.length; for (int i = 0; i < targetCount; ++i) { if (!isSameInstruction(specInsnHolder1.targets[i], specInsnHolder2.targets[i])) { return false; } } return true; } case InstructionCodec.INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD: { FillArrayDataPayloadInstructionHolder specInsnHolder1 = (FillArrayDataPayloadInstructionHolder) insnHolder1; FillArrayDataPayloadInstructionHolder specInsnHolder2 = (FillArrayDataPayloadInstructionHolder) insnHolder2; if (specInsnHolder1.elementWidth != specInsnHolder2.elementWidth) { return false; } if (specInsnHolder1.size != specInsnHolder2.size) { return false; } int elementWidth = specInsnHolder1.elementWidth; switch (elementWidth) { case 1: { byte[] array1 = (byte[]) specInsnHolder1.data; byte[] array2 = (byte[]) specInsnHolder2.data; return CompareUtils.uArrCompare(array1, array2) == 0; } case 2: { short[] array1 = (short[]) specInsnHolder1.data; short[] array2 = (short[]) specInsnHolder2.data; return CompareUtils.uArrCompare(array1, array2) == 0; } case 4: { int[] array1 = (int[]) specInsnHolder1.data; int[] array2 = (int[]) specInsnHolder2.data; return CompareUtils.uArrCompare(array1, array2) == 0; } case 8: { long[] array1 = (long[]) specInsnHolder1.data; long[] array2 = (long[]) specInsnHolder2.data; return CompareUtils.sArrCompare(array1, array2) == 0; } default: { throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); } } } default: { if (insnHolder1.literal != insnHolder2.literal) { return false; } if (insnHolder1.registerCount != insnHolder2.registerCount) { return false; } if (insnHolder1.a != insnHolder2.a) { return false; } if (insnHolder1.b != insnHolder2.b) { return false; } if (insnHolder1.c != insnHolder2.c) { return false; } if (insnHolder1.d != insnHolder2.d) { return false; } if (insnHolder1.e != insnHolder2.e) { return false; } return Arrays.equals(insnHolder1.registers, insnHolder2.registers); } } } private boolean compareIndex(int opcode, int index1, int index2) { final int indexType = InstructionCodec.getInstructionIndexType(opcode); switch (indexType) { case InstructionCodec.INDEX_TYPE_STRING_REF: { return compareString(index1, index2); } case InstructionCodec.INDEX_TYPE_TYPE_REF: { return compareType(index1, index2); } case InstructionCodec.INDEX_TYPE_FIELD_REF: { return compareField(index1, index2); } case InstructionCodec.INDEX_TYPE_METHOD_REF: { return compareMethod(index1, index2); } case InstructionCodec.INDEX_TYPE_CALL_SITE_REF: { return compareCallSite(index1, index2); } case InstructionCodec.INDEX_TYPE_METHOD_HANDLE_REF: { return compareMethodHandle(index1, index2); } case InstructionCodec.INDEX_TYPE_PROTO_REF: { return compareProto(index1, index2); } default: { throw new IllegalArgumentException("Unknown index type " + indexType + " for opcode " + Hex.u1(opcode)); } } } protected abstract boolean compareString(int stringIndex1, int stringIndex2); protected abstract boolean compareType(int typeIndex1, int typeIndex2); protected abstract boolean compareField(int fieldIndex1, int fieldIndex2); protected abstract boolean compareMethod(int methodIndex1, int methodIndex2); protected abstract boolean compareCallSite(int callsiteIndex1, int callsiteIndex2); protected abstract boolean compareMethodHandle(int methodHandleIndex1, int methodHandleIndex2); protected abstract boolean compareProto(int protoIndex1, int protoIndex2); private static class InstructionHolder { int insnFormat = InstructionCodec.INSN_FORMAT_UNKNOWN; int address = -1; int opcode = -1; int index = 0; int target = 0; long literal = 0L; int registerCount = 0; int a = 0; int b = 0; int c = 0; int d = 0; int e = 0; int protoIndex = 0; int[] registers = null; } private static class SparseSwitchPayloadInsntructionHolder extends InstructionHolder { int[] keys = null; int[] targets = null; } private static class PackedSwitchPayloadInsntructionHolder extends InstructionHolder { int firstKey = 0; int[] targets = null; } private static class FillArrayDataPayloadInstructionHolder extends InstructionHolder { Object data = null; int size = 0; int elementWidth = 0; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionPromoter.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import com.tencent.tinker.android.dex.DexException; import com.tencent.tinker.android.dx.util.Hex; import com.tencent.tinker.android.utils.SparseIntArray; /** * *** This file is NOT a part of AOSP. *** * * Created by tangyinsheng on 2016/8/11. */ public final class InstructionPromoter extends InstructionVisitor { private final SparseIntArray addressMap = new SparseIntArray(); // Notice that the unit of currentPromotedAddress is not 'byte' // but 'short' private int currentPromotedAddress = 0; public InstructionPromoter() { super(null); } private void mapAddressIfNeeded(int currentAddress) { if (currentAddress != this.currentPromotedAddress) { addressMap.append(currentAddress, this.currentPromotedAddress); } } public int getPromotedAddress(int currentAddress) { int index = addressMap.indexOfKey(currentAddress); if (index < 0) { return currentAddress; } else { return addressMap.valueAt(index); } } public int getPromotedAddressCount() { return addressMap.size(); } @Override public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.SPECIAL_FORMAT: case Opcodes.NOP: case Opcodes.RETURN_VOID: { this.currentPromotedAddress += 1; break; } case Opcodes.GOTO: { int relativeTarget = InstructionCodec.getTarget(target, this.currentPromotedAddress); if (relativeTarget != (byte) relativeTarget) { if (relativeTarget != (short) relativeTarget) { this.currentPromotedAddress += 3; } else { this.currentPromotedAddress += 2; } } else { this.currentPromotedAddress += 1; } break; } case Opcodes.GOTO_16: { int relativeTarget = InstructionCodec.getTarget(target, this.currentPromotedAddress); if (relativeTarget != (short) relativeTarget) { this.currentPromotedAddress += 3; } else { this.currentPromotedAddress += 2; } break; } case Opcodes.GOTO_32: { this.currentPromotedAddress += 3; break; } case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.CONST_STRING: { if (index > 0xFFFF) { this.currentPromotedAddress += 3; } else { this.currentPromotedAddress += 2; } break; } case Opcodes.CONST_STRING_JUMBO: { this.currentPromotedAddress += 3; break; } case Opcodes.CONST_4: case Opcodes.MOVE_RESULT: case Opcodes.MOVE_RESULT_WIDE: case Opcodes.MOVE_RESULT_OBJECT: case Opcodes.MOVE_EXCEPTION: case Opcodes.RETURN: case Opcodes.RETURN_WIDE: case Opcodes.RETURN_OBJECT: case Opcodes.MONITOR_ENTER: case Opcodes.MONITOR_EXIT: case Opcodes.THROW: { this.currentPromotedAddress += 1; break; } case Opcodes.IF_EQZ: case Opcodes.IF_NEZ: case Opcodes.IF_LTZ: case Opcodes.IF_GEZ: case Opcodes.IF_GTZ: case Opcodes.IF_LEZ: case Opcodes.CONST_16: case Opcodes.CONST_WIDE_16: case Opcodes.CONST_HIGH16: case Opcodes.CONST_WIDE_HIGH16: case Opcodes.CONST_CLASS: case Opcodes.CHECK_CAST: case Opcodes.NEW_INSTANCE: case Opcodes.SGET: case Opcodes.SGET_WIDE: case Opcodes.SGET_OBJECT: case Opcodes.SGET_BOOLEAN: case Opcodes.SGET_BYTE: case Opcodes.SGET_CHAR: case Opcodes.SGET_SHORT: case Opcodes.SPUT: case Opcodes.SPUT_WIDE: case Opcodes.SPUT_OBJECT: case Opcodes.SPUT_BOOLEAN: case Opcodes.SPUT_BYTE: case Opcodes.SPUT_CHAR: case Opcodes.SPUT_SHORT: case Opcodes.CONST_METHOD_HANDLE: case Opcodes.CONST_METHOD_TYPE: { this.currentPromotedAddress += 2; break; } case Opcodes.CONST: case Opcodes.CONST_WIDE_32: case Opcodes.FILL_ARRAY_DATA: case Opcodes.PACKED_SWITCH: case Opcodes.SPARSE_SWITCH: case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: { this.currentPromotedAddress += 3; break; } case Opcodes.CONST_WIDE: { this.currentPromotedAddress += 5; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.MOVE: case Opcodes.MOVE_WIDE: case Opcodes.MOVE_OBJECT: case Opcodes.ARRAY_LENGTH: case Opcodes.NEG_INT: case Opcodes.NOT_INT: case Opcodes.NEG_LONG: case Opcodes.NOT_LONG: case Opcodes.NEG_FLOAT: case Opcodes.NEG_DOUBLE: case Opcodes.INT_TO_LONG: case Opcodes.INT_TO_FLOAT: case Opcodes.INT_TO_DOUBLE: case Opcodes.LONG_TO_INT: case Opcodes.LONG_TO_FLOAT: case Opcodes.LONG_TO_DOUBLE: case Opcodes.FLOAT_TO_INT: case Opcodes.FLOAT_TO_LONG: case Opcodes.FLOAT_TO_DOUBLE: case Opcodes.DOUBLE_TO_INT: case Opcodes.DOUBLE_TO_LONG: case Opcodes.DOUBLE_TO_FLOAT: case Opcodes.INT_TO_BYTE: case Opcodes.INT_TO_CHAR: case Opcodes.INT_TO_SHORT: case Opcodes.ADD_INT_2ADDR: case Opcodes.SUB_INT_2ADDR: case Opcodes.MUL_INT_2ADDR: case Opcodes.DIV_INT_2ADDR: case Opcodes.REM_INT_2ADDR: case Opcodes.AND_INT_2ADDR: case Opcodes.OR_INT_2ADDR: case Opcodes.XOR_INT_2ADDR: case Opcodes.SHL_INT_2ADDR: case Opcodes.SHR_INT_2ADDR: case Opcodes.USHR_INT_2ADDR: case Opcodes.ADD_LONG_2ADDR: case Opcodes.SUB_LONG_2ADDR: case Opcodes.MUL_LONG_2ADDR: case Opcodes.DIV_LONG_2ADDR: case Opcodes.REM_LONG_2ADDR: case Opcodes.AND_LONG_2ADDR: case Opcodes.OR_LONG_2ADDR: case Opcodes.XOR_LONG_2ADDR: case Opcodes.SHL_LONG_2ADDR: case Opcodes.SHR_LONG_2ADDR: case Opcodes.USHR_LONG_2ADDR: case Opcodes.ADD_FLOAT_2ADDR: case Opcodes.SUB_FLOAT_2ADDR: case Opcodes.MUL_FLOAT_2ADDR: case Opcodes.DIV_FLOAT_2ADDR: case Opcodes.REM_FLOAT_2ADDR: case Opcodes.ADD_DOUBLE_2ADDR: case Opcodes.SUB_DOUBLE_2ADDR: case Opcodes.MUL_DOUBLE_2ADDR: case Opcodes.DIV_DOUBLE_2ADDR: case Opcodes.REM_DOUBLE_2ADDR: { this.currentPromotedAddress += 1; break; } case Opcodes.MOVE_FROM16: case Opcodes.MOVE_WIDE_FROM16: case Opcodes.MOVE_OBJECT_FROM16: { this.currentPromotedAddress += 2; break; } case Opcodes.ADD_INT_LIT8: case Opcodes.RSUB_INT_LIT8: case Opcodes.MUL_INT_LIT8: case Opcodes.DIV_INT_LIT8: case Opcodes.REM_INT_LIT8: case Opcodes.AND_INT_LIT8: case Opcodes.OR_INT_LIT8: case Opcodes.XOR_INT_LIT8: case Opcodes.SHL_INT_LIT8: case Opcodes.SHR_INT_LIT8: case Opcodes.USHR_INT_LIT8: case Opcodes.IF_EQ: case Opcodes.IF_NE: case Opcodes.IF_LT: case Opcodes.IF_GE: case Opcodes.IF_GT: case Opcodes.IF_LE: case Opcodes.ADD_INT_LIT16: case Opcodes.RSUB_INT: case Opcodes.MUL_INT_LIT16: case Opcodes.DIV_INT_LIT16: case Opcodes.REM_INT_LIT16: case Opcodes.AND_INT_LIT16: case Opcodes.OR_INT_LIT16: case Opcodes.XOR_INT_LIT16: case Opcodes.INSTANCE_OF: case Opcodes.NEW_ARRAY: case Opcodes.IGET: case Opcodes.IGET_WIDE: case Opcodes.IGET_OBJECT: case Opcodes.IGET_BOOLEAN: case Opcodes.IGET_BYTE: case Opcodes.IGET_CHAR: case Opcodes.IGET_SHORT: case Opcodes.IPUT: case Opcodes.IPUT_WIDE: case Opcodes.IPUT_OBJECT: case Opcodes.IPUT_BOOLEAN: case Opcodes.IPUT_BYTE: case Opcodes.IPUT_CHAR: case Opcodes.IPUT_SHORT: { this.currentPromotedAddress += 2; break; } case Opcodes.MOVE_16: case Opcodes.MOVE_WIDE_16: case Opcodes.MOVE_OBJECT_16: case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.CMPL_FLOAT: case Opcodes.CMPG_FLOAT: case Opcodes.CMPL_DOUBLE: case Opcodes.CMPG_DOUBLE: case Opcodes.CMP_LONG: case Opcodes.AGET: case Opcodes.AGET_WIDE: case Opcodes.AGET_OBJECT: case Opcodes.AGET_BOOLEAN: case Opcodes.AGET_BYTE: case Opcodes.AGET_CHAR: case Opcodes.AGET_SHORT: case Opcodes.APUT: case Opcodes.APUT_WIDE: case Opcodes.APUT_OBJECT: case Opcodes.APUT_BOOLEAN: case Opcodes.APUT_BYTE: case Opcodes.APUT_CHAR: case Opcodes.APUT_SHORT: case Opcodes.ADD_INT: case Opcodes.SUB_INT: case Opcodes.MUL_INT: case Opcodes.DIV_INT: case Opcodes.REM_INT: case Opcodes.AND_INT: case Opcodes.OR_INT: case Opcodes.XOR_INT: case Opcodes.SHL_INT: case Opcodes.SHR_INT: case Opcodes.USHR_INT: case Opcodes.ADD_LONG: case Opcodes.SUB_LONG: case Opcodes.MUL_LONG: case Opcodes.DIV_LONG: case Opcodes.REM_LONG: case Opcodes.AND_LONG: case Opcodes.OR_LONG: case Opcodes.XOR_LONG: case Opcodes.SHL_LONG: case Opcodes.SHR_LONG: case Opcodes.USHR_LONG: case Opcodes.ADD_FLOAT: case Opcodes.SUB_FLOAT: case Opcodes.MUL_FLOAT: case Opcodes.DIV_FLOAT: case Opcodes.REM_FLOAT: case Opcodes.ADD_DOUBLE: case Opcodes.SUB_DOUBLE: case Opcodes.MUL_DOUBLE: case Opcodes.DIV_DOUBLE: case Opcodes.REM_DOUBLE: { this.currentPromotedAddress += 2; break; } case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.FILLED_NEW_ARRAY: case Opcodes.INVOKE_VIRTUAL: case Opcodes.INVOKE_SUPER: case Opcodes.INVOKE_DIRECT: case Opcodes.INVOKE_STATIC: case Opcodes.INVOKE_INTERFACE: case Opcodes.INVOKE_CUSTOM: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } @Override public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { mapAddressIfNeeded(currentAddress); switch (opcode) { case Opcodes.FILLED_NEW_ARRAY_RANGE: case Opcodes.INVOKE_VIRTUAL_RANGE: case Opcodes.INVOKE_SUPER_RANGE: case Opcodes.INVOKE_DIRECT_RANGE: case Opcodes.INVOKE_STATIC_RANGE: case Opcodes.INVOKE_INTERFACE_RANGE: case Opcodes.INVOKE_CUSTOM_RANGE: { this.currentPromotedAddress += 3; break; } default: { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } } public void visitInvokePolymorphicInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int protoIndex, int[] registers) { if (opcode == Opcodes.INVOKE_POLYMORPHIC) { this.currentPromotedAddress += 4; } else { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } public void visitInvokePolymorphicRangeInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int c, int registerCount, int protoIndex) { if (opcode == Opcodes.INVOKE_POLYMORPHIC_RANGE) { this.currentPromotedAddress += 4; } else { throw new IllegalStateException("unexpected opcode: " + Hex.u2or4(opcode)); } } @Override public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { mapAddressIfNeeded(currentAddress); this.currentPromotedAddress += 2; this.currentPromotedAddress += keys.length * 2; this.currentPromotedAddress += targets.length * 2; } @Override public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { mapAddressIfNeeded(currentAddress); this.currentPromotedAddress += 2 + 2; this.currentPromotedAddress += targets.length * 2; } @Override public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { mapAddressIfNeeded(currentAddress); this.currentPromotedAddress += 2 + 2; switch (elementWidth) { case 1: { int length = ((byte[]) data).length; this.currentPromotedAddress += (length >> 1) + (length & 1); break; } case 2: { this.currentPromotedAddress += ((short[]) data).length * 1; break; } case 4: { this.currentPromotedAddress += ((int[]) data).length * 2; break; } case 8: { this.currentPromotedAddress += ((long[]) data).length * 4; break; } default: { throw new DexException("bogus element_width: " + Hex.u2(elementWidth)); } } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionReader.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import java.io.EOFException; /** * *** This file is NOT a part of AOSP. *** * * Created by tangyinsheng on 2016/5/26. */ public final class InstructionReader { private final ShortArrayCodeInput codeIn; public InstructionReader(ShortArrayCodeInput in) { this.codeIn = in; } public void accept(InstructionVisitor iv) throws EOFException { InstructionCodec.decode(codeIn, iv); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionVisitor.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; /** * *** This file is NOT a part of AOSP. *** * * Created by tangyinsheng on 2016/5/26. */ public class InstructionVisitor { private final InstructionVisitor prevIv; public InstructionVisitor(InstructionVisitor iv) { this.prevIv = iv; } public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { if (prevIv != null) { prevIv.visitZeroRegisterInsn(currentAddress, opcode, index, indexType, target, literal); } } public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { if (prevIv != null) { prevIv.visitOneRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a); } } public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { if (prevIv != null) { prevIv.visitTwoRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b); } } public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { if (prevIv != null) { prevIv.visitThreeRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c); } } public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { if (prevIv != null) { prevIv.visitFourRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c, d); } } public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { if (prevIv != null) { prevIv.visitFiveRegisterInsn(currentAddress, opcode, index, indexType, target, literal, a, b, c, d, e); } } public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { if (prevIv != null) { prevIv.visitRegisterRangeInsn(currentAddress, opcode, index, indexType, target, literal, a, registerCount); } } public void visitInvokePolymorphicInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int protoIndex, int[] registers) { if (prevIv != null) { prevIv.visitInvokePolymorphicInstruction(currentAddress, opcode, methodIndex, indexType, protoIndex, registers); } } public void visitInvokePolymorphicRangeInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int c, int registerCount, int protoIndex) { if (prevIv != null) { prevIv.visitInvokePolymorphicRangeInstruction(currentAddress, opcode, methodIndex, indexType, c, registerCount, protoIndex); } } public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { if (prevIv != null) { prevIv.visitSparseSwitchPayloadInsn(currentAddress, opcode, keys, targets); } } public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { if (prevIv != null) { prevIv.visitPackedSwitchPayloadInsn(currentAddress, opcode, firstKey, targets); } } public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { if (prevIv != null) { prevIv.visitFillArrayDataPayloadInsn(currentAddress, opcode, data, size, elementWidth); } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/InstructionWriter.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import static com.tencent.tinker.android.dx.instruction.InstructionCodec.getTarget; import com.tencent.tinker.android.dex.DexException; import com.tencent.tinker.android.dx.util.Hex; /** * *** This file is NOT a part of AOSP. *** * * Created by tangyinsheng on 2016/5/27. */ public final class InstructionWriter extends InstructionVisitor { private final ShortArrayCodeOutput codeOut; private final InstructionPromoter insnPromoter; private final boolean hasPromoter; /* *** Used by InstructionCodec *** */ int currOpcode = 0; int currIndex = 0; int currTarget = 0; long currLiteral = 0L; int currRegisterCount = 0; int currRegA = 0; int currRegB = 0; int currRegC = 0; int currRegD = 0; int currRegE = 0; int currRegF = 0; int currRegG = 0; int currProtoIndex = 0; /* for INSN_FORMAT_45CC & INSN_FORMAT_4RCC encoding */ int[] currKeys = null; /* for INSN_FORMAT_SPARSE_SWITCH_PAYLOAD encoding */ int[] currTargets = null; /* for INSN_FORMAT_SPARSE_SWITCH_PAYLOAD encoding */ int currFirstKey = 0; /* for INSN_FORMAT_PACKED_SWITCH_PAYLOAD encoding */ int currElementWidth = 0; /* for INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD encoding */ Object currData = null; /* for INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD encoding */ int currSize = 0; /* for INSN_FORMAT_FILL_ARRAY_DATA_PAYLOAD encoding */ /* ********************************* */ public InstructionWriter(ShortArrayCodeOutput codeOut, InstructionPromoter ipmo) { super(null); this.codeOut = codeOut; this.insnPromoter = ipmo; this.hasPromoter = (ipmo != null); } public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { if (this.hasPromoter) { target = this.insnPromoter.getPromotedAddress(target); switch (opcode) { case Opcodes.GOTO: { int relativeTarget = getTarget(target, codeOut.cursor()); if (relativeTarget != (byte) relativeTarget) { if (relativeTarget == (short) relativeTarget) { opcode = Opcodes.GOTO_16; } else { opcode = Opcodes.GOTO_32; } } break; } case Opcodes.GOTO_16: { int relativeTarget = getTarget(target, codeOut.cursor()); if (relativeTarget != (short) relativeTarget) { opcode = Opcodes.GOTO_32; } break; } default: { break; } } } currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 0; currRegA = 0; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { if (this.hasPromoter) { target = this.insnPromoter.getPromotedAddress(target); } if (opcode == Opcodes.CONST_STRING) { if (this.hasPromoter) { if (index > 0xFFFF) { opcode = Opcodes.CONST_STRING_JUMBO; } } else { if (index > 0xFFFF) { throw new DexException("string index out of bound: " + Hex.u4(index) + ", perhaps you need to enable force jumbo mode."); } } } currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 1; currRegA = a; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { if (this.hasPromoter) { target = this.insnPromoter.getPromotedAddress(target); } currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 2; currRegA = a; currRegB = b; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 3; currRegA = a; currRegB = b; currRegC = c; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 4; currRegA = a; currRegB = b; currRegC = c; currRegD = d; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = 5; currRegA = a; currRegB = b; currRegC = c; currRegD = d; currRegE = e; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { currOpcode = opcode; currIndex = index; currTarget = target; currLiteral = literal; currRegisterCount = registerCount; currRegA = a; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } @Override public void visitInvokePolymorphicInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int protoIndex, int[] registers) { currOpcode = opcode; currIndex = methodIndex; currProtoIndex = protoIndex; currRegisterCount = registers.length; currRegA = 0; currRegB = 0; currRegC = registers.length > 0 ? registers[0] : 0; currRegD = registers.length > 1 ? registers[1] : 0; currRegE = registers.length > 2 ? registers[2] : 0; currRegF = registers.length > 3 ? registers[3] : 0; currRegG = registers.length > 4 ? registers[4] : 0; InstructionCodec.encode(codeOut, this); } @Override public void visitInvokePolymorphicRangeInstruction(int currentAddress, int opcode, int methodIndex, int indexType, int c, int registerCount, int protoIndex) { currOpcode = opcode; currIndex = methodIndex; currRegisterCount = registerCount; currRegA = 0; currRegB = 0; currRegC = c; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; currProtoIndex = protoIndex; InstructionCodec.encode(codeOut, this); } public void visitSparseSwitchPayloadInsn(int currentAddress, int opcode, int[] keys, int[] targets) { currOpcode = opcode; currKeys = keys; if (this.hasPromoter) { currTargets = new int[targets.length]; for (int i = 0; i < targets.length; ++i) { currTargets[i] = this.insnPromoter.getPromotedAddress(targets[i]); } } else { currTargets = targets; } currRegisterCount = 0; currRegA = 0; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitPackedSwitchPayloadInsn(int currentAddress, int opcode, int firstKey, int[] targets) { currOpcode = opcode; currFirstKey = firstKey; if (this.hasPromoter) { currTargets = new int[targets.length]; for (int i = 0; i < targets.length; ++i) { currTargets[i] = this.insnPromoter.getPromotedAddress(targets[i]); } } else { currTargets = targets; } currRegisterCount = 0; currRegA = 0; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } public void visitFillArrayDataPayloadInsn(int currentAddress, int opcode, Object data, int size, int elementWidth) { currOpcode = opcode; currData = data; currSize = size; currElementWidth = elementWidth; currRegisterCount = 0; currRegA = 0; currRegB = 0; currRegC = 0; currRegD = 0; currRegE = 0; currRegF = 0; currRegG = 0; InstructionCodec.encode(codeOut, this); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/Opcodes.java ================================================ /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; /** * All the Dalvik opcode value constants. See the related spec * document for the meaning and instruction format of each opcode. */ public final class Opcodes { /** * pseudo-opcode used for nonstandard format payload "instructions". TODO: * Retire this concept, and start treating the payload instructions * more like the rest. */ public static final int SPECIAL_FORMAT = -1; // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen public static final int NOP = 0x00; public static final int MOVE = 0x01; public static final int MOVE_FROM16 = 0x02; public static final int MOVE_16 = 0x03; public static final int MOVE_WIDE = 0x04; public static final int MOVE_WIDE_FROM16 = 0x05; public static final int MOVE_WIDE_16 = 0x06; public static final int MOVE_OBJECT = 0x07; public static final int MOVE_OBJECT_FROM16 = 0x08; public static final int MOVE_OBJECT_16 = 0x09; public static final int MOVE_RESULT = 0x0a; public static final int MOVE_RESULT_WIDE = 0x0b; public static final int MOVE_RESULT_OBJECT = 0x0c; public static final int MOVE_EXCEPTION = 0x0d; public static final int RETURN_VOID = 0x0e; public static final int RETURN = 0x0f; public static final int RETURN_WIDE = 0x10; public static final int RETURN_OBJECT = 0x11; public static final int CONST_4 = 0x12; public static final int CONST_16 = 0x13; public static final int CONST = 0x14; public static final int CONST_HIGH16 = 0x15; public static final int CONST_WIDE_16 = 0x16; public static final int CONST_WIDE_32 = 0x17; public static final int CONST_WIDE = 0x18; public static final int CONST_WIDE_HIGH16 = 0x19; public static final int CONST_STRING = 0x1a; public static final int CONST_STRING_JUMBO = 0x1b; public static final int CONST_CLASS = 0x1c; public static final int MONITOR_ENTER = 0x1d; public static final int MONITOR_EXIT = 0x1e; public static final int CHECK_CAST = 0x1f; public static final int INSTANCE_OF = 0x20; public static final int ARRAY_LENGTH = 0x21; public static final int NEW_INSTANCE = 0x22; public static final int NEW_ARRAY = 0x23; public static final int FILLED_NEW_ARRAY = 0x24; public static final int FILLED_NEW_ARRAY_RANGE = 0x25; public static final int FILL_ARRAY_DATA = 0x26; public static final int THROW = 0x27; public static final int GOTO = 0x28; public static final int GOTO_16 = 0x29; public static final int GOTO_32 = 0x2a; public static final int PACKED_SWITCH = 0x2b; public static final int SPARSE_SWITCH = 0x2c; public static final int CMPL_FLOAT = 0x2d; public static final int CMPG_FLOAT = 0x2e; public static final int CMPL_DOUBLE = 0x2f; public static final int CMPG_DOUBLE = 0x30; public static final int CMP_LONG = 0x31; public static final int IF_EQ = 0x32; public static final int IF_NE = 0x33; public static final int IF_LT = 0x34; public static final int IF_GE = 0x35; public static final int IF_GT = 0x36; public static final int IF_LE = 0x37; public static final int IF_EQZ = 0x38; public static final int IF_NEZ = 0x39; public static final int IF_LTZ = 0x3a; public static final int IF_GEZ = 0x3b; public static final int IF_GTZ = 0x3c; public static final int IF_LEZ = 0x3d; public static final int AGET = 0x44; public static final int AGET_WIDE = 0x45; public static final int AGET_OBJECT = 0x46; public static final int AGET_BOOLEAN = 0x47; public static final int AGET_BYTE = 0x48; public static final int AGET_CHAR = 0x49; public static final int AGET_SHORT = 0x4a; public static final int APUT = 0x4b; public static final int APUT_WIDE = 0x4c; public static final int APUT_OBJECT = 0x4d; public static final int APUT_BOOLEAN = 0x4e; public static final int APUT_BYTE = 0x4f; public static final int APUT_CHAR = 0x50; public static final int APUT_SHORT = 0x51; public static final int IGET = 0x52; public static final int IGET_WIDE = 0x53; public static final int IGET_OBJECT = 0x54; public static final int IGET_BOOLEAN = 0x55; public static final int IGET_BYTE = 0x56; public static final int IGET_CHAR = 0x57; public static final int IGET_SHORT = 0x58; public static final int IPUT = 0x59; public static final int IPUT_WIDE = 0x5a; public static final int IPUT_OBJECT = 0x5b; public static final int IPUT_BOOLEAN = 0x5c; public static final int IPUT_BYTE = 0x5d; public static final int IPUT_CHAR = 0x5e; public static final int IPUT_SHORT = 0x5f; public static final int SGET = 0x60; public static final int SGET_WIDE = 0x61; public static final int SGET_OBJECT = 0x62; public static final int SGET_BOOLEAN = 0x63; public static final int SGET_BYTE = 0x64; public static final int SGET_CHAR = 0x65; public static final int SGET_SHORT = 0x66; public static final int SPUT = 0x67; public static final int SPUT_WIDE = 0x68; public static final int SPUT_OBJECT = 0x69; public static final int SPUT_BOOLEAN = 0x6a; public static final int SPUT_BYTE = 0x6b; public static final int SPUT_CHAR = 0x6c; public static final int SPUT_SHORT = 0x6d; public static final int INVOKE_VIRTUAL = 0x6e; public static final int INVOKE_SUPER = 0x6f; public static final int INVOKE_DIRECT = 0x70; public static final int INVOKE_STATIC = 0x71; public static final int INVOKE_INTERFACE = 0x72; public static final int INVOKE_VIRTUAL_RANGE = 0x74; public static final int INVOKE_SUPER_RANGE = 0x75; public static final int INVOKE_DIRECT_RANGE = 0x76; public static final int INVOKE_STATIC_RANGE = 0x77; public static final int INVOKE_INTERFACE_RANGE = 0x78; public static final int NEG_INT = 0x7b; public static final int NOT_INT = 0x7c; public static final int NEG_LONG = 0x7d; public static final int NOT_LONG = 0x7e; public static final int NEG_FLOAT = 0x7f; public static final int NEG_DOUBLE = 0x80; public static final int INT_TO_LONG = 0x81; public static final int INT_TO_FLOAT = 0x82; public static final int INT_TO_DOUBLE = 0x83; public static final int LONG_TO_INT = 0x84; public static final int LONG_TO_FLOAT = 0x85; public static final int LONG_TO_DOUBLE = 0x86; public static final int FLOAT_TO_INT = 0x87; public static final int FLOAT_TO_LONG = 0x88; public static final int FLOAT_TO_DOUBLE = 0x89; public static final int DOUBLE_TO_INT = 0x8a; public static final int DOUBLE_TO_LONG = 0x8b; public static final int DOUBLE_TO_FLOAT = 0x8c; public static final int INT_TO_BYTE = 0x8d; public static final int INT_TO_CHAR = 0x8e; public static final int INT_TO_SHORT = 0x8f; public static final int ADD_INT = 0x90; public static final int SUB_INT = 0x91; public static final int MUL_INT = 0x92; public static final int DIV_INT = 0x93; public static final int REM_INT = 0x94; public static final int AND_INT = 0x95; public static final int OR_INT = 0x96; public static final int XOR_INT = 0x97; public static final int SHL_INT = 0x98; public static final int SHR_INT = 0x99; public static final int USHR_INT = 0x9a; public static final int ADD_LONG = 0x9b; public static final int SUB_LONG = 0x9c; public static final int MUL_LONG = 0x9d; public static final int DIV_LONG = 0x9e; public static final int REM_LONG = 0x9f; public static final int AND_LONG = 0xa0; public static final int OR_LONG = 0xa1; public static final int XOR_LONG = 0xa2; public static final int SHL_LONG = 0xa3; public static final int SHR_LONG = 0xa4; public static final int USHR_LONG = 0xa5; public static final int ADD_FLOAT = 0xa6; public static final int SUB_FLOAT = 0xa7; public static final int MUL_FLOAT = 0xa8; public static final int DIV_FLOAT = 0xa9; public static final int REM_FLOAT = 0xaa; public static final int ADD_DOUBLE = 0xab; public static final int SUB_DOUBLE = 0xac; public static final int MUL_DOUBLE = 0xad; public static final int DIV_DOUBLE = 0xae; public static final int REM_DOUBLE = 0xaf; public static final int ADD_INT_2ADDR = 0xb0; public static final int SUB_INT_2ADDR = 0xb1; public static final int MUL_INT_2ADDR = 0xb2; public static final int DIV_INT_2ADDR = 0xb3; public static final int REM_INT_2ADDR = 0xb4; public static final int AND_INT_2ADDR = 0xb5; public static final int OR_INT_2ADDR = 0xb6; public static final int XOR_INT_2ADDR = 0xb7; public static final int SHL_INT_2ADDR = 0xb8; public static final int SHR_INT_2ADDR = 0xb9; public static final int USHR_INT_2ADDR = 0xba; public static final int ADD_LONG_2ADDR = 0xbb; public static final int SUB_LONG_2ADDR = 0xbc; public static final int MUL_LONG_2ADDR = 0xbd; public static final int DIV_LONG_2ADDR = 0xbe; public static final int REM_LONG_2ADDR = 0xbf; public static final int AND_LONG_2ADDR = 0xc0; public static final int OR_LONG_2ADDR = 0xc1; public static final int XOR_LONG_2ADDR = 0xc2; public static final int SHL_LONG_2ADDR = 0xc3; public static final int SHR_LONG_2ADDR = 0xc4; public static final int USHR_LONG_2ADDR = 0xc5; public static final int ADD_FLOAT_2ADDR = 0xc6; public static final int SUB_FLOAT_2ADDR = 0xc7; public static final int MUL_FLOAT_2ADDR = 0xc8; public static final int DIV_FLOAT_2ADDR = 0xc9; public static final int REM_FLOAT_2ADDR = 0xca; public static final int ADD_DOUBLE_2ADDR = 0xcb; public static final int SUB_DOUBLE_2ADDR = 0xcc; public static final int MUL_DOUBLE_2ADDR = 0xcd; public static final int DIV_DOUBLE_2ADDR = 0xce; public static final int REM_DOUBLE_2ADDR = 0xcf; public static final int ADD_INT_LIT16 = 0xd0; public static final int RSUB_INT = 0xd1; public static final int MUL_INT_LIT16 = 0xd2; public static final int DIV_INT_LIT16 = 0xd3; public static final int REM_INT_LIT16 = 0xd4; public static final int AND_INT_LIT16 = 0xd5; public static final int OR_INT_LIT16 = 0xd6; public static final int XOR_INT_LIT16 = 0xd7; public static final int ADD_INT_LIT8 = 0xd8; public static final int RSUB_INT_LIT8 = 0xd9; public static final int MUL_INT_LIT8 = 0xda; public static final int DIV_INT_LIT8 = 0xdb; public static final int REM_INT_LIT8 = 0xdc; public static final int AND_INT_LIT8 = 0xdd; public static final int OR_INT_LIT8 = 0xde; public static final int XOR_INT_LIT8 = 0xdf; public static final int SHL_INT_LIT8 = 0xe0; public static final int SHR_INT_LIT8 = 0xe1; public static final int USHR_INT_LIT8 = 0xe2; public static final int INVOKE_POLYMORPHIC = 0xfa; public static final int INVOKE_POLYMORPHIC_RANGE = 0xfb; public static final int INVOKE_CUSTOM = 0xfc; public static final int INVOKE_CUSTOM_RANGE = 0xfd; public static final int CONST_METHOD_HANDLE = 0xfe; public static final int CONST_METHOD_TYPE = 0xff; // END(opcodes) // TODO: Generate these payload opcodes with opcode-gen. /** * special pseudo-opcode value for packed-switch data payload * instructions */ public static final int PACKED_SWITCH_PAYLOAD = 0x100; /** special pseudo-opcode value for packed-switch data payload * instructions */ public static final int SPARSE_SWITCH_PAYLOAD = 0x200; /** special pseudo-opcode value for fill-array-data data payload * instructions */ public static final int FILL_ARRAY_DATA_PAYLOAD = 0x300; /** * This class is uninstantiable. */ private Opcodes() { // This space intentionally left blank. } /** * Gets the opcode out of an opcode unit, the latter of which may also * include one or more argument values. * * @param opcodeUnit the opcode-containing code unit * @return the extracted opcode */ public static int extractOpcodeFromUnit(int opcodeUnit) { /* * Note: This method bakes in knowledge that all opcodes are * either single-byte or of the forms (byteValue << 8) or * ((byteValue << 8) | 0xff). */ int lowByte = opcodeUnit & 0xff; return ((lowByte == 0) || (lowByte == 0xff)) ? opcodeUnit : lowByte; } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeInput.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; import java.io.EOFException; /** * Reads code from a {@code short[]}. */ public final class ShortArrayCodeInput extends CodeCursor { /** source array to read from */ private final short[] array; /** * Constructs an instance. */ public ShortArrayCodeInput(short[] array) { if (array == null) { throw new NullPointerException("array == null"); } this.array = array; } /** * Returns whether there are any more code units to read. This * is analogous to {@code hasNext()} on an interator. */ public boolean hasMore() { return cursor() < array.length; } /** * Reads a code unit. */ public int read() throws EOFException { try { int value = array[cursor()]; advance(1); return value & 0xffff; } catch (ArrayIndexOutOfBoundsException ex) { throw new EOFException(); } } /** * Reads two code units, treating them as a little-endian {@code int}. */ public int readInt() throws EOFException { int short0 = read(); int short1 = read(); return short0 | (short1 << 16); } /** * Reads four code units, treating them as a little-endian {@code long}. */ public long readLong() throws EOFException { long short0 = read(); long short1 = read(); long short2 = read(); long short3 = read(); return short0 | (short1 << 16) | (short2 << 32) | (short3 << 48); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/instruction/ShortArrayCodeOutput.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.instruction; /** * Writes code to a {@code short[]}. */ public final class ShortArrayCodeOutput extends CodeCursor { /** array to write to */ private short[] array; /** * Constructs an instance. * * @param initSize the maximum number of code units that will be written */ public ShortArrayCodeOutput(int initSize) { if (initSize < 0) { throw new IllegalArgumentException("initSize < 0"); } this.array = new short[initSize]; } /** * Constructs an instance by wrapping an exist array. * @param array the array to write. */ public ShortArrayCodeOutput(short[] array) { if (array == null) { throw new IllegalArgumentException("array is null."); } this.array = array; } /** * Gets the array. The returned array contains exactly the data * written (e.g. no leftover space at the end). */ public short[] getArray() { int cursor = cursor(); if (cursor == array.length) { return array; } short[] result = new short[cursor]; System.arraycopy(array, 0, result, 0, cursor); return result; } /** * Writes a code unit. */ public void write(short codeUnit) { ensureArrayLength(1); array[cursor()] = codeUnit; advance(1); } /** * Writes two code units. */ public void write(short u0, short u1) { write(u0); write(u1); } /** * Writes three code units. */ public void write(short u0, short u1, short u2) { write(u0); write(u1); write(u2); } /** * Writes four code units. */ public void write(short u0, short u1, short u2, short u3) { write(u0); write(u1); write(u2); write(u3); } /** * Writes five code units. */ public void write(short u0, short u1, short u2, short u3, short u4) { write(u0); write(u1); write(u2); write(u3); write(u4); } /** * Writes an {@code int}, little-endian. */ public void writeInt(int value) { write((short) value); write((short) (value >> 16)); } /** * Writes a {@code long}, little-endian. */ public void writeLong(long value) { write((short) value); write((short) (value >> 16)); write((short) (value >> 32)); write((short) (value >> 48)); } /** * Writes the contents of the given array. */ public void write(byte[] data) { int value = 0; boolean even = true; for (byte b : data) { if (even) { value = b & 0xff; even = false; } else { value |= b << 8; write((short) value); even = true; } } if (!even) { write((short) value); } } /** * Writes the contents of the given array. */ public void write(short[] data) { for (short unit : data) { write(unit); } } /** * Writes the contents of the given array. */ public void write(int[] data) { for (int i : data) { writeInt(i); } } /** * Writes the contents of the given array. */ public void write(long[] data) { for (long l : data) { writeLong(l); } } private void ensureArrayLength(int shortCountToWrite) { int currPos = cursor(); if (array.length - currPos < shortCountToWrite) { short[] newArray = new short[array.length + (array.length >> 1)]; System.arraycopy(array, 0, newArray, 0, currPos); array = newArray; } } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dx/util/Hex.java ================================================ /* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.dx.util; /** * Utilities for formatting numbers as hexadecimal. */ public final class Hex { /** * This class is uninstantiable. */ private Hex() { // This space intentionally left blank. } /** * Formats a {@code long} as an 8-byte unsigned hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String u8(long v) { char[] result = new char[16]; for (int i = 0; i < 16; i++) { result[15 - i] = Character.forDigit((int) v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 4-byte unsigned hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String u4(int v) { char[] result = new char[8]; for (int i = 0; i < 8; i++) { result[7 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 3-byte unsigned hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String u3(int v) { char[] result = new char[6]; for (int i = 0; i < 6; i++) { result[5 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 2-byte unsigned hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String u2(int v) { char[] result = new char[4]; for (int i = 0; i < 4; i++) { result[3 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as either a 2-byte unsigned hex value * (if the value is small enough) or a 4-byte unsigned hex value (if * not). * * @param v value to format * @return {@code non-null;} formatted form */ public static String u2or4(int v) { if (v == (char) v) { return u2(v); } else { return u4(v); } } /** * Formats an {@code int} as a 1-byte unsigned hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String u1(int v) { char[] result = new char[2]; for (int i = 0; i < 2; i++) { result[1 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 4-bit unsigned hex nibble. * * @param v value to format * @return {@code non-null;} formatted form */ public static String uNibble(int v) { char[] result = new char[1]; result[0] = Character.forDigit(v & 0x0f, 16); return new String(result); } /** * Formats a {@code long} as an 8-byte signed hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String s8(long v) { char[] result = new char[17]; if (v < 0) { result[0] = '-'; v = -v; } else { result[0] = '+'; } for (int i = 0; i < 16; i++) { result[16 - i] = Character.forDigit((int) v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 4-byte signed hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String s4(int v) { char[] result = new char[9]; if (v < 0) { result[0] = '-'; v = -v; } else { result[0] = '+'; } for (int i = 0; i < 8; i++) { result[8 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 2-byte signed hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String s2(int v) { char[] result = new char[5]; if (v < 0) { result[0] = '-'; v = -v; } else { result[0] = '+'; } for (int i = 0; i < 4; i++) { result[4 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats an {@code int} as a 1-byte signed hex value. * * @param v value to format * @return {@code non-null;} formatted form */ public static String s1(int v) { char[] result = new char[3]; if (v < 0) { result[0] = '-'; v = -v; } else { result[0] = '+'; } for (int i = 0; i < 2; i++) { result[2 - i] = Character.forDigit(v & 0x0f, 16); v >>= 4; } return new String(result); } /** * Formats a hex dump of a portion of a {@code byte[]}. The result * is always newline-terminated, unless the passed-in length was zero, * in which case the result is always the empty string ({@code ""}). * * @param arr {@code non-null;} array to format * @param offset {@code >= 0;} offset to the part to dump * @param length {@code >= 0;} number of bytes to dump * @param outOffset {@code >= 0;} first output offset to print * @param bpl {@code >= 0;} number of bytes of output per line * @param addressLength {@code {2,4,6,8};} number of characters for each address * header * @return {@code non-null;} a string of the dump */ public static String dump(byte[] arr, int offset, int length, int outOffset, int bpl, int addressLength) { int end = offset + length; // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) if (((offset | length | end) < 0) || (end > arr.length)) { throw new IndexOutOfBoundsException("arr.length " + arr.length + "; " + offset + "..!" + end); } if (outOffset < 0) { throw new IllegalArgumentException("outOffset < 0"); } if (length == 0) { return ""; } StringBuffer sb = new StringBuffer(length * 4 + 6); int col = 0; while (length > 0) { if (col == 0) { String astr; switch (addressLength) { case 2: astr = Hex.u1(outOffset); break; case 4: astr = Hex.u2(outOffset); break; case 6: astr = Hex.u3(outOffset); break; default: astr = Hex.u4(outOffset); break; } sb.append(astr); sb.append(": "); } else if ((col & 1) == 0) { sb.append(' '); } sb.append(Hex.u1(arr[offset])); outOffset++; offset++; col++; if (col == bpl) { sb.append('\n'); col = 0; } length--; } if (col != 0) { sb.append('\n'); } return sb.toString(); } public static String toHexString(byte[] ubytes) { StringBuilder strBuilder = new StringBuilder(ubytes.length << 1); for (byte b : ubytes) { strBuilder.append(u1(b)); } return strBuilder.toString(); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/utils/SparseBoolArray.java ================================================ /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.utils; /** * SparseBoolArrays map integers to booleans. Unlike a normal array of booleans, * there can be gaps in the indices. It is intended to be more memory efficient * than using a HashMap to map Integers to Booleans, both because it avoids * auto-boxing keys and values and its data structure doesn't rely on an extra entry object * for each mapping. * *

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

* *

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

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

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

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

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

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

This implementation composes a string by iterating over its mappings. */ @Override public String toString() { if (size() <= 0) { return "{}"; } StringBuilder buffer = new StringBuilder(mSize * 28); buffer.append('{'); for (int i = 0; i < mSize; i++) { if (i > 0) { buffer.append(", "); } int key = keyAt(i); buffer.append(key); buffer.append('='); boolean value = valueAt(i); buffer.append(value); } buffer.append('}'); return buffer.toString(); } } ================================================ FILE: third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/utils/SparseIntArray.java ================================================ /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.android.utils; /** * SparseIntArrays map integers to integers. Unlike a normal array of integers, * there can be gaps in the indices. It is intended to be more memory efficient * than using a HashMap to map Integers to Integers, both because it avoids * auto-boxing keys and values and its data structure doesn't rely on an extra entry object * for each mapping. * *

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

* *

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

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

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

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

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

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

This implementation composes a string by iterating over its mappings. */ @Override public String toString() { if (size() <= 0) { return "{}"; } StringBuilder buffer = new StringBuilder(mSize * 28); buffer.append('{'); for (int i = 0; i < mSize; i++) { if (i > 0) { buffer.append(", "); } int key = keyAt(i); buffer.append(key); buffer.append('='); int value = valueAt(i); buffer.append(value); } buffer.append('}'); return buffer.toString(); } } ================================================ FILE: third-party/bsdiff-util/.gitignore ================================================ /build ================================================ FILE: third-party/bsdiff-util/LICENSE.txt ================================================ BSD License Copyright (C) 2016 THL A29 Limited, a Tencent company. Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) Copyright 2003-2005 Colin Percival All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted providing that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: third-party/bsdiff-util/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from('build/libs') { include '*.jar' exclude '*javadoc.jar' exclude '*-sources.jar' } into(rootProject.file("buildSdk/android")) } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: third-party/bsdiff-util/gradle.properties ================================================ POM_ARTIFACT_ID=bsdiff-util POM_NAME=BsDiff Util POM_PACKAGING=jar ================================================ FILE: third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSDiff.java ================================================ /* * Copyright (C) 2016 THL A29 Limited, a Tencent company. * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) * Copyright 2003-2005 Colin Percival * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.tencent.tinker.bsdiff; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Stack; import java.util.zip.GZIPOutputStream; /** * Java Binary Diff utility. Based on bsdiff (v4.2) by Colin Percival (see http://www.daemonology.net/bsdiff/ ) and distributed under BSD license. * Running this on large files will probably require an increase of the default maximum heap size (use java -Xmx300m) */ public class BSDiff { //private static final String VERSION = "jbdiff-0.1.0.1"; // This is private static final byte[] MAGIC_BYTES = new byte[]{0x4D, 0x69, 0x63, 0x72, 0x6F, 0x4D, 0x73, 0x67}; private static void split(int[] arrayI, int[] arrayV, int start, int len, int h) { final int STM_ENTER = 0x00; final int STM_RECURSIVE_CALLSITE1_NEXT = 0x01; final int STM_EXIT = 0x02; class EmuStackFrame { int stmRetLabel; int start, len, h; int i, j, k, x, jj, kk; EmuStackFrame(int stmRetLabel, int start, int len, int h) { this.stmRetLabel = stmRetLabel; this.start = start; this.len = len; this.h = h; this.i = 0; this.j = 0; this.k = 0; this.x = 0; this.jj = 0; this.kk = 0; } } final Stack emuStack = new Stack<>(); emuStack.push(new EmuStackFrame(STM_EXIT, start, len, h)); int stmLabel = STM_ENTER; while (!emuStack.empty()) { final EmuStackFrame currFrame = emuStack.peek(); switch (stmLabel) { case STM_ENTER: { if (currFrame.len < 16) { for (currFrame.k = currFrame.start; currFrame.k < currFrame.start + currFrame.len; currFrame.k += currFrame.j) { currFrame.j = 1; currFrame.x = arrayV[arrayI[currFrame.k] + currFrame.h]; for (currFrame.i = 1; currFrame.k + currFrame.i < currFrame.start + currFrame.len; currFrame.i++) { if (arrayV[arrayI[currFrame.k + currFrame.i] + currFrame.h] < currFrame.x) { currFrame.x = arrayV[arrayI[currFrame.k + currFrame.i] + currFrame.h]; currFrame.j = 0; } if (arrayV[arrayI[currFrame.k + currFrame.i] + currFrame.h] == currFrame.x) { int tmp = arrayI[currFrame.k + currFrame.j]; arrayI[currFrame.k + currFrame.j] = arrayI[currFrame.k + currFrame.i]; arrayI[currFrame.k + currFrame.i] = tmp; currFrame.j++; } } for (currFrame.i = 0; currFrame.i < currFrame.j; currFrame.i++) { arrayV[arrayI[currFrame.k + currFrame.i]] = currFrame.k + currFrame.j - 1; } if (currFrame.j == 1) { arrayI[currFrame.k] = -1; } } stmLabel = STM_EXIT; continue; } currFrame.x = arrayV[arrayI[currFrame.start + currFrame.len / 2] + currFrame.h]; currFrame.jj = 0; currFrame.kk = 0; for (currFrame.i = currFrame.start; currFrame.i < currFrame.start + currFrame.len; currFrame.i++) { if (arrayV[arrayI[currFrame.i] + currFrame.h] < currFrame.x) { currFrame.jj++; } if (arrayV[arrayI[currFrame.i] + currFrame.h] == currFrame.x) { currFrame.kk++; } } currFrame.jj += currFrame.start; currFrame.kk += currFrame.jj; currFrame.i = currFrame.start; currFrame.j = 0; currFrame.k = 0; while (currFrame.i < currFrame.jj) { if (arrayV[arrayI[currFrame.i] + currFrame.h] < currFrame.x) { currFrame.i++; } else if (arrayV[arrayI[currFrame.i] + currFrame.h] == currFrame.x) { int tmp = arrayI[currFrame.i]; arrayI[currFrame.i] = arrayI[currFrame.jj + currFrame.j]; arrayI[currFrame.jj + currFrame.j] = tmp; currFrame.j++; } else { int tmp = arrayI[currFrame.i]; arrayI[currFrame.i] = arrayI[currFrame.kk + currFrame.k]; arrayI[currFrame.kk + currFrame.k] = tmp; currFrame.k++; } } while (currFrame.jj + currFrame.j < currFrame.kk) { if (arrayV[arrayI[currFrame.jj + currFrame.j] + currFrame.h] == currFrame.x) { currFrame.j++; } else { int tmp = arrayI[currFrame.jj + currFrame.j]; arrayI[currFrame.jj + currFrame.j] = arrayI[currFrame.kk + currFrame.k]; arrayI[currFrame.kk + currFrame.k] = tmp; currFrame.k++; } } stmLabel = STM_RECURSIVE_CALLSITE1_NEXT; if (currFrame.jj > currFrame.start) { // split(arrayI, arrayV, start, jj - currFrame.start, h); emuStack.push(new EmuStackFrame(stmLabel, currFrame.start, currFrame.jj - currFrame.start, currFrame.h)); stmLabel = STM_ENTER; continue; } break; } case STM_RECURSIVE_CALLSITE1_NEXT: { for (currFrame.i = 0; currFrame.i < currFrame.kk - currFrame.jj; currFrame.i++) { arrayV[arrayI[currFrame.jj + currFrame.i]] = currFrame.kk - 1; } if (currFrame.jj == currFrame.kk - 1) { arrayI[currFrame.jj] = -1; } stmLabel = STM_EXIT; if (currFrame.start + currFrame.len > currFrame.kk) { // split(arrayI, arrayV, kk, start + len - kk, h); emuStack.push(new EmuStackFrame(stmLabel, currFrame.kk, currFrame.start + currFrame.len - currFrame.kk, currFrame.h)); stmLabel = STM_ENTER; continue; } break; } case STM_EXIT: default: { stmLabel = currFrame.stmRetLabel; emuStack.pop(); break; } } } } // private static void old_split(int[] arrayI, int[] arrayV, int start, int len, int h) { // // int i, j, k, x, tmp, jj, kk; // // if (len < 16) { // for (k = start; k < start + len; k += j) { // j = 1; // x = arrayV[arrayI[k] + h]; // for (i = 1; k + i < start + len; i++) { // if (arrayV[arrayI[k + i] + h] < x) { // x = arrayV[arrayI[k + i] + h]; // j = 0; // } // // if (arrayV[arrayI[k + i] + h] == x) { // tmp = arrayI[k + j]; // arrayI[k + j] = arrayI[k + i]; // arrayI[k + i] = tmp; // j++; // } // // } // // for (i = 0; i < j; i++) { // arrayV[arrayI[k + i]] = k + j - 1; // } // if (j == 1) { // arrayI[k] = -1; // } // } // // return; // } // // x = arrayV[arrayI[start + len / 2] + h]; // jj = 0; // kk = 0; // for (i = start; i < start + len; i++) { // if (arrayV[arrayI[i] + h] < x) { // jj++; // } // if (arrayV[arrayI[i] + h] == x) { // kk++; // } // } // // jj += start; // kk += jj; // // i = start; // j = 0; // k = 0; // while (i < jj) { // if (arrayV[arrayI[i] + h] < x) { // i++; // } else if (arrayV[arrayI[i] + h] == x) { // tmp = arrayI[i]; // arrayI[i] = arrayI[jj + j]; // arrayI[jj + j] = tmp; // j++; // } else { // tmp = arrayI[i]; // arrayI[i] = arrayI[kk + k]; // arrayI[kk + k] = tmp; // k++; // } // // } // // while (jj + j < kk) { // if (arrayV[arrayI[jj + j] + h] == x) { // j++; // } else { // tmp = arrayI[jj + j]; // arrayI[jj + j] = arrayI[kk + k]; // arrayI[kk + k] = tmp; // k++; // } // // } // // if (jj > start) { // old_split(arrayI, arrayV, start, jj - start, h); // } // // for (i = 0; i < kk - jj; i++) { // arrayV[arrayI[jj + i]] = kk - 1; // } // // if (jj == kk - 1) { // arrayI[jj] = -1; // } // // if (start + len > kk) { // old_split(arrayI, arrayV, kk, start + len - kk, h); // } // // } /** * Fast suffix sporting. Larsson and Sadakane's qsufsort algorithm. See * http://www.cs.lth.se/Research/Algorithms/Papers/jesper5.ps */ private static void qsufsort(int[] arrayI, int[] arrayV, byte[] oldBuf, int oldsize) { // int oldsize = oldBuf.length; int[] buckets = new int[256]; // No need to do that in Java. // for ( int i = 0; i < 256; i++ ) { // buckets[i] = 0; // } for (int i = 0; i < oldsize; i++) { buckets[oldBuf[i] & 0xff]++; } for (int i = 1; i < 256; i++) { buckets[i] += buckets[i - 1]; } for (int i = 255; i > 0; i--) { buckets[i] = buckets[i - 1]; } buckets[0] = 0; for (int i = 0; i < oldsize; i++) { arrayI[++buckets[oldBuf[i] & 0xff]] = i; } arrayI[0] = oldsize; for (int i = 0; i < oldsize; i++) { arrayV[i] = buckets[oldBuf[i] & 0xff]; } arrayV[oldsize] = 0; for (int i = 1; i < 256; i++) { if (buckets[i] == buckets[i - 1] + 1) { arrayI[buckets[i]] = -1; } } arrayI[0] = -1; for (int h = 1; arrayI[0] != -(oldsize + 1); h += h) { int len = 0; int i; for (i = 0; i < oldsize + 1;) { if (arrayI[i] < 0) { len -= arrayI[i]; i -= arrayI[i]; } else { // if(len) I[i-len]=-len; if (len != 0) { arrayI[i - len] = -len; } len = arrayV[arrayI[i]] + 1 - i; split(arrayI, arrayV, i, len, h); i += len; len = 0; } } if (len != 0) { arrayI[i - len] = -len; } } for (int i = 0; i < oldsize + 1; i++) { arrayI[arrayV[i]] = i; } } /** * 分别将 oldBufd[start..oldSize] 和 oldBufd[end..oldSize] 与 newBuf[newBufOffset...newSize] 进行匹配, * 返回他们中的最长匹配长度,并且将最长匹配的开始位置记录到pos.value中。 */ private static int search(int[] arrayI, byte[] oldBuf, int oldSize, byte[] newBuf, int newSize, int newBufOffset, int start, int end, IntByRef pos) { if (end - start < 2) { int x = matchlen(oldBuf, oldSize, arrayI[start], newBuf, newSize, newBufOffset); int y = matchlen(oldBuf, oldSize, arrayI[end], newBuf, newSize, newBufOffset); if (x > y) { pos.value = arrayI[start]; return x; } else { pos.value = arrayI[end]; return y; } } // binary search int x = start + (end - start) / 2; if (memcmp(oldBuf, oldSize, arrayI[x], newBuf, newSize, newBufOffset) < 0) { return search(arrayI, oldBuf, oldSize, newBuf, newSize, newBufOffset, x, end, pos); // Calls itself recursively } else { return search(arrayI, oldBuf, oldSize, newBuf, newSize, newBufOffset, start, x, pos); } } /** * Count the number of bytes that match in oldBuf[oldOffset...oldSize] and newBuf[newOffset...newSize] */ private static int matchlen(byte[] oldBuf, int oldSize, int oldOffset, byte[] newBuf, int newSize, int newOffset) { int end = Math.min(oldSize - oldOffset, newSize - newOffset); for (int i = 0; i < end; i++) { if (oldBuf[oldOffset + i] != newBuf[newOffset + i]) { return i; } } return end; } /** * Compare two byte array segments to see if they are equal * * return 1 if s1[s1offset...s1Size] is bigger than s2[s2offset...s2Size] otherwise return -1 */ private static int memcmp(byte[] s1, int s1Size, int s1offset, byte[] s2, int s2Size, int s2offset) { int n = s1Size - s1offset; if (n > (s2Size - s2offset)) { n = s2Size - s2offset; } for (int i = 0; i < n; i++) { if (s1[i + s1offset] != s2[i + s2offset]) { return s1[i + s1offset] < s2[i + s2offset] ? -1 : 1; } } return 0; } public static void bsdiff(File oldFile, File newFile, File diffFile) throws IOException { InputStream oldInputStream = new BufferedInputStream(new FileInputStream(oldFile)); InputStream newInputStream = new BufferedInputStream(new FileInputStream(newFile)); OutputStream diffOutputStream = new FileOutputStream(diffFile); try { byte[] diffBytes = bsdiff(oldInputStream, (int) oldFile.length(), newInputStream, (int) newFile.length()); diffOutputStream.write(diffBytes); } finally { diffOutputStream.close(); } } public static byte[] bsdiff(InputStream oldInputStream, int oldsize, InputStream newInputStream, int newsize) throws IOException { byte[] oldBuf = new byte[oldsize]; BSUtil.readFromStream(oldInputStream, oldBuf, 0, oldsize); oldInputStream.close(); byte[] newBuf = new byte[newsize]; BSUtil.readFromStream(newInputStream, newBuf, 0, newsize); newInputStream.close(); return bsdiff(oldBuf, oldsize, newBuf, newsize); } public static byte[] bsdiff(byte[] oldBuf, int oldsize, byte[] newBuf, int newsize) throws IOException { int[] arrayI = new int[oldsize + 1]; qsufsort(arrayI, new int[oldsize + 1], oldBuf, oldsize); // diff block int diffBLockLen = 0; byte[] diffBlock = new byte[newsize]; // extra block int extraBlockLen = 0; byte[] extraBlock = new byte[newsize]; /* * Diff file is composed as follows: * * Header (32 bytes) Data (from offset 32 to end of file) * * Header: * Offset 0, length 8 bytes: file magic "MicroMsg" * Offset 8, length 8 bytes: length of compressed ctrl block * Offset 16, length 8 bytes: length of compressed diff block * Offset 24, length 8 bytes: length of new file * * Data: * 32 (length ctrlBlockLen): ctrlBlock (bzip2) * 32 + ctrlBlockLen (length diffBlockLen): diffBlock (bzip2) * 32 + ctrlBlockLen + diffBlockLen (to end of file): extraBlock (bzip2) * * ctrlBlock comprises a set of records, each record 12 bytes. * A record comprises 3 x 32 bit integers. The ctrlBlock is not compressed. */ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); DataOutputStream diffOut = new DataOutputStream(byteOut); // Write as much of header as we have now. Size of ctrlBlock and diffBlock must be filled in later. diffOut.write(MAGIC_BYTES); diffOut.writeLong(-1); // place holder for ctrlBlockLen diffOut.writeLong(-1); // place holder for diffBlockLen diffOut.writeLong(newsize); diffOut.flush(); GZIPOutputStream bzip2Out = new GZIPOutputStream(diffOut); DataOutputStream dataOut = new DataOutputStream(bzip2Out); int oldscore, scsc; int overlap, ss, lens; int i; int scan = 0; int matchLen = 0; int lastscan = 0; int lastpos = 0; int lastoffset = 0; IntByRef pos = new IntByRef(); // int ctrlBlockLen = 0; while (scan < newsize) { oldscore = 0; for (scsc = scan += matchLen; scan < newsize; scan++) { // oldBuf[0...oldsize] newBuf[scan...newSize]. pos.value,scan matchLen = search(arrayI, oldBuf, oldsize, newBuf, newsize, scan, 0, oldsize, pos); for (; scsc < scan + matchLen; scsc++) { if ((scsc + lastoffset < oldsize) && (oldBuf[scsc + lastoffset] == newBuf[scsc])) { oldscore++; } } if (((matchLen == oldscore) && (matchLen != 0)) || (matchLen > oldscore + 8)) { break; } if ((scan + lastoffset < oldsize) && (oldBuf[scan + lastoffset] == newBuf[scan])) { oldscore--; } } if ((matchLen != oldscore) || (scan == newsize)) { int equalNum = 0; int sf = 0; int lenFromOld = 0; for (i = 0; (lastscan + i < scan) && (lastpos + i < oldsize);) { if (oldBuf[lastpos + i] == newBuf[lastscan + i]) { equalNum++; } i++; if (equalNum * 2 - i > sf * 2 - lenFromOld) { sf = equalNum; lenFromOld = i; } } int lenb = 0; if (scan < newsize) { equalNum = 0; int sb = 0; for (i = 1; (scan >= lastscan + i) && (pos.value >= i); i++) { if (oldBuf[pos.value - i] == newBuf[scan - i]) { equalNum++; } if (equalNum * 2 - i > sb * 2 - lenb) { sb = equalNum; lenb = i; } } } if (lastscan + lenFromOld > scan - lenb) { overlap = (lastscan + lenFromOld) - (scan - lenb); equalNum = 0; ss = 0; lens = 0; for (i = 0; i < overlap; i++) { if (newBuf[lastscan + lenFromOld - overlap + i] == oldBuf[lastpos + lenFromOld - overlap + i]) { equalNum++; } if (newBuf[scan - lenb + i] == oldBuf[pos.value - lenb + i]) { equalNum--; } if (equalNum > ss) { ss = equalNum; lens = i + 1; } } lenFromOld += lens - overlap; lenb -= lens; } // ? byte casting introduced here -- might affect things for (i = 0; i < lenFromOld; i++) { diffBlock[diffBLockLen + i] = (byte) (newBuf[lastscan + i] - oldBuf[lastpos + i]); } for (i = 0; i < (scan - lenb) - (lastscan + lenFromOld); i++) { extraBlock[extraBlockLen + i] = newBuf[lastscan + lenFromOld + i]; } diffBLockLen += lenFromOld; extraBlockLen += (scan - lenb) - (lastscan + lenFromOld); // Write control block entry (3 x int) dataOut.writeInt(lenFromOld); // oldBuf dataOut.writeInt((scan - lenb) - (lastscan + lenFromOld)); // diffBufextraBlock dataOut.writeInt((pos.value - lenb) - (lastpos + lenFromOld)); // oldBuf lastscan = scan - lenb; lastpos = pos.value - lenb; lastoffset = pos.value - scan; } // end if } // end while loop dataOut.flush(); bzip2Out.finish(); // now compressed ctrlBlockLen int ctrlBlockLen = diffOut.size() - BSUtil.HEADER_SIZE; // GZIPOutputStream gzOut; /* * Write diff block */ bzip2Out = new GZIPOutputStream(diffOut); bzip2Out.write(diffBlock, 0, diffBLockLen); bzip2Out.finish(); bzip2Out.flush(); int diffBlockLen = diffOut.size() - ctrlBlockLen - BSUtil.HEADER_SIZE; // System.err.println( "Diff: diffBlockLen=" + diffBlockLen ); /* * Write extra block */ bzip2Out = new GZIPOutputStream(diffOut); bzip2Out.write(extraBlock, 0, extraBlockLen); bzip2Out.finish(); bzip2Out.flush(); diffOut.close(); /* * Write missing header info. */ ByteArrayOutputStream byteHeaderOut = new ByteArrayOutputStream(BSUtil.HEADER_SIZE); DataOutputStream headerOut = new DataOutputStream(byteHeaderOut); headerOut.write(MAGIC_BYTES); headerOut.writeLong(ctrlBlockLen); // place holder for ctrlBlockLen headerOut.writeLong(diffBlockLen); // place holder for diffBlockLen headerOut.writeLong(newsize); headerOut.close(); // Copy header information into the diff byte[] diffBytes = byteOut.toByteArray(); byte[] headerBytes = byteHeaderOut.toByteArray(); System.arraycopy(headerBytes, 0, diffBytes, 0, headerBytes.length); return diffBytes; } // /** // * Run JBDiff from the command line. Params: oldfile newfile difffile. diff // * file will be created. // */ // public static void main(String[] arg) throws IOException { // // if (arg.length != 3) { // System.err.println("usage example: java -Xmx250m JBDiff oldfile newfile patchfile\n"); // return; // } // File oldFile = new File(arg[0]); // File newFile = new File(arg[1]); // File diffFile = new File(arg[2]); // // bsdiff(oldFile, newFile, diffFile); // // } public static void main(String[] args) throws IOException { final File oldFile = new File("/Users/tomystang/bsdiff-test/old/classes.dex"); final File newFile = new File("/Users/tomystang/bsdiff-test/new/classes.dex"); final File diffFile = new File("/Users/tomystang/bsdiff-test/test_bsdiff.diff"); bsdiff(oldFile, newFile, diffFile); } private static class IntByRef { private int value; } } ================================================ FILE: third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java ================================================ /* * Copyright (C) 2016 THL A29 Limited, a Tencent company. * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) * Copyright 2003-2005 Colin Percival * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.tencent.tinker.bsdiff; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.zip.GZIPInputStream; /** * Java Binary patcher (based on bspatch by Joe Desbonnet, joe@galway.net (JBPatch)) */ public class BSPatch { /** * the patch process is end up successfully */ public static final int RETURN_SUCCESS = 1; /** * diffFile is null, or the diffFile does not exist */ public static final int RETURN_DIFF_FILE_ERR = 2; /** * oldFile is null, or the oldFile does not exist */ public static final int RETURN_OLD_FILE_ERR = 3; /** * newFile is null, or can not create the newFile */ public static final int RETURN_NEW_FILE_ERR = 4; /** * BSPatch using less memory size. * Memory size = diffFile size + max block size * */ public static int patchLessMemory(RandomAccessFile oldFile, File newFile, File diffFile, int extLen) throws IOException { if (oldFile == null || oldFile.length() <= 0) { return RETURN_OLD_FILE_ERR; } if (newFile == null) { return RETURN_NEW_FILE_ERR; } if (diffFile == null || diffFile.length() <= 0) { return RETURN_DIFF_FILE_ERR; } byte[] diffBytes = new byte[(int) diffFile.length()]; InputStream diffInputStream = new FileInputStream(diffFile); try { BSUtil.readFromStream(diffInputStream, diffBytes, 0, diffBytes.length); } finally { diffInputStream.close(); } return patchLessMemory(oldFile, (int) oldFile.length(), diffBytes, diffBytes.length, newFile, extLen); } /** * BSPatch using less memory size. * Memory size = diffFile size + max block size * extLen the length of the apk external info. set 0 if has no external info. */ public static int patchLessMemory(RandomAccessFile oldFile, int oldsize, byte[] diffBuf, int diffSize, File newFile, int extLen) throws IOException { if (oldFile == null || oldsize <= 0) { return RETURN_OLD_FILE_ERR; } if (newFile == null) { return RETURN_NEW_FILE_ERR; } if (diffBuf == null || diffSize <= 0) { return RETURN_DIFF_FILE_ERR; } // int commentLenPos = oldsize - extLen - 2; // if (commentLenPos <= 2) { // return RETURN_OLD_FILE_ERR; // } DataInputStream diffIn = new DataInputStream(new ByteArrayInputStream(diffBuf, 0, diffSize)); diffIn.skip(8); // skip headerMagic at header offset 0 (length 8 bytes) long ctrlBlockLen = diffIn.readLong(); // ctrlBlockLen after bzip2 compression at heater offset 8 (length 8 bytes) long diffBlockLen = diffIn.readLong(); // diffBlockLen after bzip2 compression at header offset 16 (length 8 bytes) int newsize = (int) diffIn.readLong(); // size of new file at header offset 24 (length 8 bytes) diffIn.close(); InputStream in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(BSUtil.HEADER_SIZE); DataInputStream ctrlBlockIn = new DataInputStream(new GZIPInputStream(in)); in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(ctrlBlockLen + BSUtil.HEADER_SIZE); InputStream diffBlockIn = new GZIPInputStream(in); in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE); InputStream extraBlockIn = new GZIPInputStream(in); OutputStream outStream = new FileOutputStream(newFile); try { int oldpos = 0; int newpos = 0; int[] ctrl = new int[3]; // int nbytes; while (newpos < newsize) { for (int i = 0; i <= 2; i++) { ctrl[i] = ctrlBlockIn.readInt(); } if (newpos + ctrl[0] > newsize) { outStream.close(); return RETURN_DIFF_FILE_ERR; } // Read ctrl[0] bytes from diffBlock stream byte[] buffer = new byte[ctrl[0]]; if (!BSUtil.readFromStream(diffBlockIn, buffer, 0, ctrl[0])) { outStream.close(); return RETURN_DIFF_FILE_ERR; } byte[] oldBuffer = new byte[ctrl[0]]; if (oldFile.read(oldBuffer, 0, ctrl[0]) < ctrl[0]) { outStream.close(); return RETURN_DIFF_FILE_ERR; } for (int i = 0; i < ctrl[0]; i++) { // if (oldpos + i == commentLenPos) { // oldBuffer[i] = 0; // oldBuffer[i + 1] = 0; // } if ((oldpos + i >= 0) && (oldpos + i < oldsize)) { buffer[i] += oldBuffer[i]; } } outStream.write(buffer); newpos += ctrl[0]; oldpos += ctrl[0]; if (newpos + ctrl[1] > newsize) { outStream.close(); return RETURN_DIFF_FILE_ERR; } buffer = new byte[ctrl[1]]; if (!BSUtil.readFromStream(extraBlockIn, buffer, 0, ctrl[1])) { outStream.close(); return RETURN_DIFF_FILE_ERR; } outStream.write(buffer); outStream.flush(); newpos += ctrl[1]; oldpos += ctrl[2]; oldFile.seek(oldpos); } ctrlBlockIn.close(); diffBlockIn.close(); extraBlockIn.close(); } finally { oldFile.close(); outStream.close(); } return RETURN_SUCCESS; } /** * This patch method is fast ,but using more memory. * Memory size = oldBuf + diffBuf + newBuf * */ public static int patchFast(File oldFile, File newFile, File diffFile, int extLen) throws IOException { if (oldFile == null || oldFile.length() <= 0) { return RETURN_OLD_FILE_ERR; } if (newFile == null) { return RETURN_NEW_FILE_ERR; } if (diffFile == null || diffFile.length() <= 0) { return RETURN_DIFF_FILE_ERR; } InputStream oldInputStream = new BufferedInputStream(new FileInputStream(oldFile)); byte[] diffBytes = new byte[(int) diffFile.length()]; InputStream diffInputStream = new FileInputStream(diffFile); try { BSUtil.readFromStream(diffInputStream, diffBytes, 0, diffBytes.length); } finally { diffInputStream.close(); } byte[] newBytes = patchFast(oldInputStream, (int) oldFile.length(), diffBytes, extLen); OutputStream newOutputStream = new FileOutputStream(newFile); try { newOutputStream.write(newBytes); } finally { newOutputStream.close(); } return RETURN_SUCCESS; } /** * This patch method is fast ,but using more memory. * Memory size = oldBuf + diffBuf + newBuf * */ public static int patchFast(InputStream oldInputStream, InputStream diffInputStream, File newFile) throws IOException { if (oldInputStream == null) { return RETURN_OLD_FILE_ERR; } if (newFile == null) { return RETURN_NEW_FILE_ERR; } if (diffInputStream == null) { return RETURN_DIFF_FILE_ERR; } byte[] oldBytes = BSUtil.inputStreamToByte(oldInputStream); byte[] diffBytes = BSUtil.inputStreamToByte(diffInputStream); byte[] newBytes = patchFast(oldBytes, oldBytes.length, diffBytes, diffBytes.length, 0); OutputStream newOutputStream = new FileOutputStream(newFile); try { newOutputStream.write(newBytes); } finally { newOutputStream.close(); } return RETURN_SUCCESS; } public static byte[] patchFast(InputStream oldInputStream, InputStream diffInputStream) throws IOException { if (oldInputStream == null) { return null; } if (diffInputStream == null) { return null; } byte[] oldBytes = BSUtil.inputStreamToByte(oldInputStream); byte[] diffBytes = BSUtil.inputStreamToByte(diffInputStream); byte[] newBytes = patchFast(oldBytes, oldBytes.length, diffBytes, diffBytes.length, 0); return newBytes; } /** * This patch method is fast ,but using more memory. * Memory size = oldBuf + diffBuf + newBuf */ public static byte[] patchFast(InputStream oldInputStream, int oldsize, byte[] diffBytes, int extLen) throws IOException { // Read in old file (file to be patched) to oldBuf byte[] oldBuf = new byte[oldsize]; BSUtil.readFromStream(oldInputStream, oldBuf, 0, oldsize); oldInputStream.close(); return BSPatch.patchFast(oldBuf, oldsize, diffBytes, diffBytes.length, extLen); } /** * This patch method is fast ,but using more memory. * Memory size = oldBuf + diffBuf + newBuf */ public static byte[] patchFast(byte[] oldBuf, int oldsize, byte[] diffBuf, int diffSize, int extLen) throws IOException { DataInputStream diffIn = new DataInputStream(new ByteArrayInputStream(diffBuf, 0, diffSize)); diffIn.skip(8); // skip headerMagic at header offset 0 (length 8 bytes) long ctrlBlockLen = diffIn.readLong(); // ctrlBlockLen after bzip2 compression at heater offset 8 (length 8 bytes) long diffBlockLen = diffIn.readLong(); // diffBlockLen after bzip2 compression at header offset 16 (length 8 bytes) int newsize = (int) diffIn.readLong(); // size of new file at header offset 24 (length 8 bytes) diffIn.close(); InputStream in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(BSUtil.HEADER_SIZE); DataInputStream ctrlBlockIn = new DataInputStream(new GZIPInputStream(in)); in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(ctrlBlockLen + BSUtil.HEADER_SIZE); InputStream diffBlockIn = new GZIPInputStream(in); in = new ByteArrayInputStream(diffBuf, 0, diffSize); in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE); InputStream extraBlockIn = new GZIPInputStream(in); // byte[] newBuf = new byte[newsize + 1]; byte[] newBuf = new byte[newsize]; int oldpos = 0; int newpos = 0; int[] ctrl = new int[3]; // int nbytes; while (newpos < newsize) { for (int i = 0; i <= 2; i++) { ctrl[i] = ctrlBlockIn.readInt(); } if (newpos + ctrl[0] > newsize) { throw new IOException("Corrupt by wrong patch file."); } // Read ctrl[0] bytes from diffBlock stream if (!BSUtil.readFromStream(diffBlockIn, newBuf, newpos, ctrl[0])) { throw new IOException("Corrupt by wrong patch file."); } for (int i = 0; i < ctrl[0]; i++) { if ((oldpos + i >= 0) && (oldpos + i < oldsize)) { newBuf[newpos + i] += oldBuf[oldpos + i]; } } newpos += ctrl[0]; oldpos += ctrl[0]; if (newpos + ctrl[1] > newsize) { throw new IOException("Corrupt by wrong patch file."); } if (!BSUtil.readFromStream(extraBlockIn, newBuf, newpos, ctrl[1])) { throw new IOException("Corrupt by wrong patch file."); } newpos += ctrl[1]; oldpos += ctrl[2]; } ctrlBlockIn.close(); diffBlockIn.close(); extraBlockIn.close(); return newBuf; } } ================================================ FILE: third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSUtil.java ================================================ /* * Copyright (C) 2016 THL A29 Limited, a Tencent company. * Copyright (c) 2005, Joe Desbonnet, (jdesbonnet@gmail.com) * Copyright 2003-2005 Colin Percival * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.tencent.tinker.bsdiff; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class BSUtil { // JBDiff extensions by Stefan.Liebig@compeople.de: // // - introduced a HEADER_SIZE constant here /** * Length of the diff file header. */ public static final int HEADER_SIZE = 32; public static final int BUFFER_SIZE = 8192; /** * Read from input stream and fill the given buffer from the given offset up * to length len. */ public static final boolean readFromStream(InputStream in, byte[] buf, int offset, int len) throws IOException { int totalBytesRead = 0; while (totalBytesRead < len) { int bytesRead = in.read(buf, offset + totalBytesRead, len - totalBytesRead); if (bytesRead < 0) { return false; } totalBytesRead += bytesRead; } return true; } /** * input stream to byte * @param in InputStream * @return byte[] * @throws IOException */ public static byte[] inputStreamToByte(InputStream in) throws IOException { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] data = new byte[BUFFER_SIZE]; int count = -1; while ((count = in.read(data, 0, BUFFER_SIZE)) != -1) { outStream.write(data, 0, count); } data = null; return outStream.toByteArray(); } } ================================================ FILE: third-party/tinker-ziputils/.gitignore ================================================ /build ================================================ FILE: third-party/tinker-ziputils/NOTICE.txt ================================================ Original work Copyright (c) 2005-2008, The Android Open Source Project Modified work Copyright (C) 2016 THL A29 Limited, a Tencent company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: third-party/tinker-ziputils/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from('build/libs') { include '*.jar' exclude '*javadoc.jar' exclude '*-sources.jar' } into(rootProject.file("buildSdk/android")) } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: third-party/tinker-ziputils/gradle.properties ================================================ # # Tencent is pleased to support the open source community by making Tinker available. # # Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. # # Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # # https://opensource.org/licenses/BSD-3-Clause # # Unless required by applicable law or agreed to in writing, software distributed under the License is # distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, # either express or implied. See the License for the specific language governing permissions and # limitations under the License. # POM_ARTIFACT_ID=tinker-ziputils POM_NAME=Tinker Zip Utils POM_PACKAGING=jar ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/AlignedZipOutputStream.java ================================================ package com.tencent.tinker.ziputils.ziputil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.HashSet; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; /** * Created by tangyinsheng on 2018/11/20. */ public class AlignedZipOutputStream extends DeflaterOutputStream { /** * Constants migrated from ZipConstants */ private static final long LOCSIG = 0x4034b50, EXTSIG = 0x8074b50, CENSIG = 0x2014b50, ENDSIG = 0x6054b50; /** * Constants migrated from ZipConstants */ private static final int LOCHDR = 30, EXTHDR = 16; // 1980-01-01 00:00:00 private static final int MOD_DATE_CONST = 0x21; private static final int TIME_CONST = 0; /** * General Purpose Bit Flags, Bit 3. * If this bit is set, the fields crc-32, compressed * size and uncompressed size are set to zero in the * local header. The correct values are put in the * data descriptor immediately following the compressed * data. (Note: PKZIP version 2.04g for DOS only * recognizes this bit for method 8 compression, newer * versions of PKZIP recognize this bit for any * compression method.) */ private static final int GPBF_DATA_DESCRIPTOR_FLAG = 1 << 3; /** * General Purpose Bit Flags, Bit 11. * Language encoding flag (EFS). If this bit is set, * the filename and comment fields for this file * must be encoded using UTF-8. */ private static final int GPBF_UTF8_FLAG = 1 << 11; private static final byte[] EMPTY_BYTE_ARRAY = {}; private static final byte[] ONE_ELEM_BYTE_ARRAY = {0}; /** * Indicates deflated entries. */ public static final int DEFLATED = ZipEntry.DEFLATED; /** * Indicates uncompressed entries. */ public static final int STORED = ZipEntry.STORED; private static final int ZIPLocalHeaderVersionNeeded = 20; private byte[] commentBytes = EMPTY_BYTE_ARRAY; private final HashSet entries = new HashSet<>(); private int defaultCompressionMethod = DEFLATED; private int compressionLevel = Deflater.DEFAULT_COMPRESSION; private ByteArrayOutputStream cDir = new ByteArrayOutputStream(); private ZipEntry currentEntry; private final CRC32 crc = new CRC32(); private long crcDataSize = 0; private int offset = 0; private int nameLength; private byte[] nameBytes; private boolean finished = false; private boolean closed = false; private final int alignBytes; private int padding = 0; /** * Constructs a new {@code ZipOutputStream} that writes a zip file * to the given {@code OutputStream}. */ public AlignedZipOutputStream(OutputStream os) { this(os, 4); } /** * Constructs a new {@code ZipOutputStream} that writes a zip file * to the given {@code OutputStream}. */ public AlignedZipOutputStream(OutputStream os, int alignBytes) { super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); this.alignBytes = alignBytes; } /** * Closes the current {@code ZipEntry}, if any, and the underlying output * stream. If the stream is already closed this method does nothing. * * @throws IOException * If an error occurs closing the stream. */ @Override public void close() throws IOException { // don't call super.close() because that calls finish() conditionally if (!closed) { finish(); def.end(); out.close(); out = null; closed = true; } } /** * Closes the current {@code ZipEntry}. Any entry terminal data is written * to the underlying stream. * * @throws IOException * If an error occurs closing the entry. */ public void closeEntry() throws IOException { checkOpen(); if (currentEntry == null) { return; } if (currentEntry.getMethod() == DEFLATED) { super.finish(); } // Verify values for STORED types if (currentEntry.getMethod() == STORED) { if (crc.getValue() != currentEntry.getCrc()) { throw new ZipException("CRC mismatch"); } if (currentEntry.getSize() != crcDataSize) { throw new ZipException("Size mismatch"); } } int curOffset = LOCHDR; // Write the DataDescriptor if (currentEntry.getMethod() != STORED) { curOffset += EXTHDR; writeLong(out, EXTSIG); currentEntry.setCrc(crc.getValue()); writeLong(out, currentEntry.getCrc()); currentEntry.setCompressedSize(def.getTotalOut()); writeLong(out, currentEntry.getCompressedSize()); currentEntry.setSize(def.getTotalIn()); writeLong(out, currentEntry.getSize()); } // Update the CentralDirectory // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = currentEntry.getMethod() == STORED ? 0 : GPBF_DATA_DESCRIPTOR_FLAG; // Since gingerbread, we always set the UTF-8 flag on individual files. // Some tools insist that the central directory also have the UTF-8 flag. // http://code.google.com/p/android/issues/detail?id=20214 flags |= GPBF_UTF8_FLAG; writeLong(cDir, CENSIG); writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version created writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version to extract writeShort(cDir, flags); writeShort(cDir, currentEntry.getMethod()); writeShort(cDir, TIME_CONST); writeShort(cDir, MOD_DATE_CONST); writeLong(cDir, crc.getValue()); if (currentEntry.getMethod() == DEFLATED) { curOffset += writeLong(cDir, def.getTotalOut()); writeLong(cDir, def.getTotalIn()); } else { curOffset += writeLong(cDir, crcDataSize); writeLong(cDir, crcDataSize); } curOffset += writeShort(cDir, nameLength); if (currentEntry.getExtra() != null) { curOffset += writeShort(cDir, currentEntry.getExtra().length); } else { writeShort(cDir, 0); } String comment = currentEntry.getComment(); byte[] commentBytes = EMPTY_BYTE_ARRAY; if (comment != null) { commentBytes = comment.getBytes(Charset.forName("UTF-8")); } writeShort(cDir, commentBytes.length); // Comment length. writeShort(cDir, 0); // Disk Start writeShort(cDir, 0); // Internal File Attributes writeLong(cDir, 0); // External File Attributes writeLong(cDir, offset); // Relative Offset of Local File Header cDir.write(nameBytes); nameBytes = null; if (currentEntry.getExtra() != null) { cDir.write(currentEntry.getExtra()); } offset += curOffset + padding; padding = 0; if (commentBytes.length > 0) { cDir.write(commentBytes); } currentEntry = null; crc.reset(); crcDataSize = 0; def.reset(); } /** * Indicates that all entries have been written to the stream. Any terminal * information is written to the underlying stream. * * @throws IOException * if an error occurs while terminating the stream. */ @Override public void finish() throws IOException { checkOpen(); if (finished) { return; } if (entries.isEmpty()) { throw new ZipException("No entries"); } if (currentEntry != null) { closeEntry(); } int cdirSize = cDir.size(); // Write Central Dir End writeLong(cDir, ENDSIG); writeShort(cDir, 0); // Disk Number writeShort(cDir, 0); // Start Disk writeShort(cDir, entries.size()); // Number of entries writeShort(cDir, entries.size()); // Number of entries writeLong(cDir, cdirSize); // Size of central dir writeLong(cDir, offset + padding); // Offset of central dir writeShort(cDir, commentBytes.length); if (commentBytes.length > 0) { cDir.write(commentBytes); } // Write the central directory. cDir.writeTo(out); cDir = null; finished = true; } private int getPaddingByteCount(ZipEntry entry, int entryFileOffset) { if (entry.getMethod() != STORED || alignBytes == 0) { return 0; } return (alignBytes - (entryFileOffset % alignBytes)) % alignBytes; } private void makePaddingToStream(OutputStream os, int padding) throws IOException { if (padding <= 0) { return; } while (padding-- > 0) { os.write(0); } } /** * Writes entry information to the underlying stream. Data associated with * the entry can then be written using {@code write()}. After data is * written {@code closeEntry()} must be called to complete the writing of * the entry to the underlying stream. * * @param ze * the {@code ZipEntry} to store. * @throws IOException * If an error occurs storing the entry. * @see #write */ public void putNextEntry(ZipEntry ze) throws IOException { if (currentEntry != null) { closeEntry(); } // Did this ZipEntry specify a method, or should we use the default? int method = ze.getMethod(); if (method == -1) { method = defaultCompressionMethod; } // If the method is STORED, check that the ZipEntry was configured appropriately. if (method == STORED) { if (ze.getCompressedSize() == -1) { ze.setCompressedSize(ze.getSize()); } else if (ze.getSize() == -1) { ze.setSize(ze.getCompressedSize()); } if (ze.getCrc() == -1) { throw new ZipException("STORED entry missing CRC"); } if (ze.getSize() == -1) { throw new ZipException("STORED entry missing size"); } if (ze.getSize() != ze.getCompressedSize()) { throw new ZipException("STORED entry size/compressed size mismatch"); } } checkOpen(); if (entries.contains(ze.getName())) { throw new ZipException("Entry already exists: " + ze.getName()); } if (entries.size() == 64*1024-1) { throw new ZipException("Too many entries for the zip file format's 16-bit entry count"); } nameBytes = ze.getName().getBytes(Charset.forName("UTF-8")); nameLength = nameBytes.length; if (nameLength > 0xffff) { throw new IllegalArgumentException("Name too long: " + nameLength + " UTF-8 bytes"); } def.setLevel(compressionLevel); ze.setMethod(method); currentEntry = ze; entries.add(currentEntry.getName()); // Local file header. // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = (method == STORED) ? 0 : GPBF_DATA_DESCRIPTOR_FLAG; // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used // modified UTF-8. From Java 7, it sets this flag and uses normal UTF-8.) flags |= GPBF_UTF8_FLAG; writeLong(out, LOCSIG); // Entry header writeShort(out, ZIPLocalHeaderVersionNeeded); // Extraction version writeShort(out, flags); writeShort(out, method); if (currentEntry.getTime() == -1) { currentEntry.setTime(System.currentTimeMillis()); } writeShort(out, TIME_CONST); writeShort(out, MOD_DATE_CONST); if (method == STORED) { writeLong(out, currentEntry.getCrc()); writeLong(out, currentEntry.getSize()); writeLong(out, currentEntry.getSize()); } else { writeLong(out, 0); writeLong(out, 0); writeLong(out, 0); } writeShort(out, nameLength); final int currDataOffset = offset + LOCHDR + nameLength + (currentEntry.getExtra() != null ? currentEntry.getExtra().length : 0); padding = getPaddingByteCount(currentEntry, currDataOffset); if (currentEntry.getExtra() != null) { writeShort(out, currentEntry.getExtra().length + padding); } else { writeShort(out, padding); } out.write(nameBytes); if (currentEntry.getExtra() != null) { out.write(currentEntry.getExtra()); } makePaddingToStream(out, padding); } /** * Sets the comment associated with the file being written. * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes. */ public void setComment(String comment) { if (comment == null) { this.commentBytes = null; return; } byte[] newCommentBytes = comment.getBytes(Charset.forName("UTF-8")); if (newCommentBytes.length > 0xffff) { throw new IllegalArgumentException("Comment too long: " + newCommentBytes.length + " bytes"); } this.commentBytes = newCommentBytes; } /** * Sets the compression level to be used * for writing entry data. */ public void setLevel(int level) { if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) { throw new IllegalArgumentException("Bad level: " + level); } compressionLevel = level; } /** * Sets the default compression method to be used when a {@code ZipEntry} doesn't * explicitly specify a method. See {@link ZipEntry#setMethod} for more details. */ public void setMethod(int method) { if (method != STORED && method != DEFLATED) { throw new IllegalArgumentException("Bad method: " + method); } defaultCompressionMethod = method; } private long writeLong(OutputStream os, long i) throws IOException { // Write out the long value as an unsigned int os.write((int) (i & 0xFF)); os.write((int) (i >> 8) & 0xFF); os.write((int) (i >> 16) & 0xFF); os.write((int) (i >> 24) & 0xFF); return i; } private int writeShort(OutputStream os, int i) throws IOException { if (i > 0xFFFF) { throw new IllegalArgumentException("value " + i + " is too large for type 'short'."); } os.write(i & 0xFF); os.write((i >> 8) & 0xFF); return i; } @Override public void write(int b) throws IOException { // Use static pre-allocated byte array to avoid memory fragment. final byte[] buf = ONE_ELEM_BYTE_ARRAY; buf[0] = (byte)(b & 0xff); write(buf, 0, 1); } /** * Writes data for the current entry to the underlying stream. * * @exception IOException * If an error occurs writing to the stream */ @Override public void write(byte[] buffer, int offset, int byteCount) throws IOException { checkOffsetAndCount(buffer.length, offset, byteCount); if (currentEntry == null) { throw new ZipException("No active entry"); } if (currentEntry.getMethod() == STORED) { out.write(buffer, offset, byteCount); } else { super.write(buffer, offset, byteCount); } crc.update(buffer, offset, byteCount); crcDataSize += byteCount; } private void checkOffsetAndCount(int arrayLength, int offset, int count) { if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { throw new ArrayIndexOutOfBoundsException("length=" + arrayLength + "; regionStart=" + offset + "; regionLength=" + count); } } private void checkOpen() throws IOException { if (closed) { throw new IOException("Stream is closed"); } } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/Arrays.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; /** * modify by zhangshaowen on 16/6/7. */ public class Arrays { public static void checkOffsetAndCount(int arrayLength, int offset, int count) { if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { // throw new ArrayIndexOutOfBoundsException(arrayLength, offset, // count); throw new ArrayIndexOutOfBoundsException(offset); } } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/BufferIterator.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; /** * modify by zhangshaowen on 16/6/7. */ public abstract class BufferIterator { /** * Seeks to the absolute position {@code offset}, measured in bytes from the start. */ public abstract void seek(int offset); /** * Skips forwards or backwards {@code byteCount} bytes from the current position. */ public abstract void skip(int byteCount); public abstract int readInt(); public abstract short readShort(); } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/HeapBufferIterator.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; import java.nio.ByteOrder; /** * Iterates over big- or little-endian bytes in a Java byte[]. * * @hide don't make this public without adding bounds checking. */ public final class HeapBufferIterator extends BufferIterator { private final byte[] buffer; private final int offset; private final int byteCount; private final ByteOrder order; private int position; HeapBufferIterator(byte[] buffer, int offset, int byteCount, ByteOrder order) { this.buffer = buffer; this.offset = offset; this.byteCount = byteCount; this.order = order; } /** * Returns a new iterator over {@code buffer}, starting at {@code offset} and continuing for * {@code byteCount} bytes. Items larger than a byte are interpreted using the given byte order. */ public static BufferIterator iterator(byte[] buffer, int offset, int byteCount, ByteOrder order) { return new HeapBufferIterator(buffer, offset, byteCount, order); } public void seek(int offset) { position = offset; } public void skip(int byteCount) { position += byteCount; } public void readByteArray(byte[] dst, int dstOffset, int byteCount) { System.arraycopy(buffer, offset + position, dst, dstOffset, byteCount); position += byteCount; } public byte readByte() { byte result = buffer[offset + position]; ++position; return result; } public int readInt() { int result = Memory.peekInt(buffer, offset + position, order); position += SizeOf.INT; return result; } public short readShort() { short result = Memory.peekShort(buffer, offset + position, order); position += SizeOf.SHORT; return result; } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/Memory.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; import java.nio.ByteOrder; /** * modify by zhangshaowen on 16/6/7. */ public final class Memory { private Memory() { } public static int peekInt(byte[] src, int offset, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { return (((src[offset++] & 0xff) << 24) | ((src[offset++] & 0xff) << 16) | ((src[offset++] & 0xff) << 8) | ((src[offset ] & 0xff) << 0)); } else { return (((src[offset++] & 0xff) << 0) | ((src[offset++] & 0xff) << 8) | ((src[offset++] & 0xff) << 16) | ((src[offset ] & 0xff) << 24)); } } public static long peekLong(byte[] src, int offset, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { int h = ((src[offset++] & 0xff) << 24) | ((src[offset++] & 0xff) << 16) | ((src[offset++] & 0xff) << 8) | ((src[offset++] & 0xff) << 0); int l = ((src[offset++] & 0xff) << 24) | ((src[offset++] & 0xff) << 16) | ((src[offset++] & 0xff) << 8) | ((src[offset ] & 0xff) << 0); return (((long) h) << 32L) | ((long) l) & 0xffffffffL; } else { int l = ((src[offset++] & 0xff) << 0) | ((src[offset++] & 0xff) << 8) | ((src[offset++] & 0xff) << 16) | ((src[offset++] & 0xff) << 24); int h = ((src[offset++] & 0xff) << 0) | ((src[offset++] & 0xff) << 8) | ((src[offset++] & 0xff) << 16) | ((src[offset ] & 0xff) << 24); return (((long) h) << 32L) | ((long) l) & 0xffffffffL; } } public static short peekShort(byte[] src, int offset, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { return (short) ((src[offset] << 8) | (src[offset + 1] & 0xff)); } else { return (short) ((src[offset + 1] << 8) | (src[offset] & 0xff)); } } public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { dst[offset++] = (byte) ((value >> 24) & 0xff); dst[offset++] = (byte) ((value >> 16) & 0xff); dst[offset++] = (byte) ((value >> 8) & 0xff); dst[offset ] = (byte) ((value >> 0) & 0xff); } else { dst[offset++] = (byte) ((value >> 0) & 0xff); dst[offset++] = (byte) ((value >> 8) & 0xff); dst[offset++] = (byte) ((value >> 16) & 0xff); dst[offset ] = (byte) ((value >> 24) & 0xff); } } public static void pokeLong(byte[] dst, int offset, long value, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { int i = (int) (value >> 32); dst[offset++] = (byte) ((i >> 24) & 0xff); dst[offset++] = (byte) ((i >> 16) & 0xff); dst[offset++] = (byte) ((i >> 8) & 0xff); dst[offset++] = (byte) ((i >> 0) & 0xff); i = (int) value; dst[offset++] = (byte) ((i >> 24) & 0xff); dst[offset++] = (byte) ((i >> 16) & 0xff); dst[offset++] = (byte) ((i >> 8) & 0xff); dst[offset ] = (byte) ((i >> 0) & 0xff); } else { int i = (int) value; dst[offset++] = (byte) ((i >> 0) & 0xff); dst[offset++] = (byte) ((i >> 8) & 0xff); dst[offset++] = (byte) ((i >> 16) & 0xff); dst[offset++] = (byte) ((i >> 24) & 0xff); i = (int) (value >> 32); dst[offset++] = (byte) ((i >> 0) & 0xff); dst[offset++] = (byte) ((i >> 8) & 0xff); dst[offset++] = (byte) ((i >> 16) & 0xff); dst[offset ] = (byte) ((i >> 24) & 0xff); } } public static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) { if (order == ByteOrder.BIG_ENDIAN) { dst[offset++] = (byte) ((value >> 8) & 0xff); dst[offset ] = (byte) ((value >> 0) & 0xff); } else { dst[offset++] = (byte) ((value >> 0) & 0xff); dst[offset ] = (byte) ((value >> 8) & 0xff); } } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/SizeOf.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; /** * modify by zhangshaowen on 16/6/7. */ public final class SizeOf { public static final int CHAR = 2; public static final int DOUBLE = 8; public static final int FLOAT = 4; public static final int INT = 4; public static final int LONG = 8; public static final int SHORT = 2; private SizeOf() { } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/StandardCharsets.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; import java.nio.charset.Charset; /** * modify by zhangshaowen on 16/6/7. */ public final class StandardCharsets { public static final Charset UTF_8 = Charset.forName("UTF-8"); private StandardCharsets() { } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/Streams.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringWriter; import java.util.concurrent.atomic.AtomicReference; //import java.util.Arrays; /** * modify by zhangshaowen on 16/6/7. */ public final class Streams { private static AtomicReference skipBuffer = new AtomicReference(); private Streams() { } /** * Implements InputStream.read(int) in terms of InputStream.read(byte[], int, int). * InputStream assumes that you implement InputStream.read(int) and provides default * implementations of the others, but often the opposite is more efficient. */ public static int readSingleByte(InputStream in) throws IOException { byte[] buffer = new byte[1]; int result = in.read(buffer, 0, 1); return (result != -1) ? buffer[0] & 0xff : -1; } /** * Implements OutputStream.write(int) in terms of OutputStream.write(byte[], int, int). * OutputStream assumes that you implement OutputStream.write(int) and provides default * implementations of the others, but often the opposite is more efficient. */ public static void writeSingleByte(OutputStream out, int b) throws IOException { byte[] buffer = new byte[1]; buffer[0] = (byte) (b & 0xff); out.write(buffer); } /** * Fills 'dst' with bytes from 'in', throwing EOFException if insufficient bytes are available. */ public static void readFully(InputStream in, byte[] dst) throws IOException { readFully(in, dst, 0, dst.length); } /** * Reads exactly 'byteCount' bytes from 'in' (into 'dst' at offset 'offset'), and throws * EOFException if insufficient bytes are available. * * Used to implement {@link java.io.DataInputStream#readFully(byte[], int, int)}. */ public static void readFully(InputStream in, byte[] dst, int offset, int byteCount) throws IOException { if (byteCount == 0) { return; } if (in == null) { throw new NullPointerException("in == null"); } if (dst == null) { throw new NullPointerException("dst == null"); } Arrays.checkOffsetAndCount(dst.length, offset, byteCount); while (byteCount > 0) { int bytesRead = in.read(dst, offset, byteCount); if (bytesRead < 0) { throw new EOFException(); } offset += bytesRead; byteCount -= bytesRead; } } /** * Returns a byte[] containing the remainder of 'in', closing it when done. */ public static byte[] readFully(InputStream in) throws IOException { try { return readFullyNoClose(in); } finally { in.close(); } } /** * Returns a byte[] containing the remainder of 'in'. */ public static byte[] readFullyNoClose(InputStream in) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int count; while ((count = in.read(buffer)) != -1) { bytes.write(buffer, 0, count); } return bytes.toByteArray(); } /** * Returns the remainder of 'reader' as a string, closing it when done. */ public static String readFully(Reader reader) throws IOException { try { StringWriter writer = new StringWriter(); char[] buffer = new char[1024]; int count; while ((count = reader.read(buffer)) != -1) { writer.write(buffer, 0, count); } return writer.toString(); } finally { reader.close(); } } public static void skipAll(InputStream in) throws IOException { do { in.skip(Long.MAX_VALUE); } while (in.read() != -1); } /** * Skip at most {@code byteCount} bytes from {@code in} by calling read * repeatedly until either the stream is exhausted or we read fewer bytes than * we ask for. * *

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

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

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

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

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

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

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

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

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

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

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

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

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

We could support mark/reset, but we don't currently need them. * * @hide */ public static class RAFStream extends InputStream { private final RandomAccessFile sharedRaf; private long endOffset; private long offset; public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) { sharedRaf = raf; offset = initialOffset; this.endOffset = endOffset; } public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException { this(raf, initialOffset, raf.length()); } @Override public int available() throws IOException { return (offset < endOffset ? 1 : 0); } @Override public int read() throws IOException { return Streams.readSingleByte(this); } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { synchronized (sharedRaf) { final long length = endOffset - offset; if (byteCount > length) { byteCount = (int) length; } sharedRaf.seek(offset); int count = sharedRaf.read(buffer, byteOffset, byteCount); if (count > 0) { offset += count; return count; } else { return -1; } } } @Override public long skip(long byteCount) throws IOException { if (byteCount > endOffset - offset) { byteCount = endOffset - offset; } offset += byteCount; return byteCount; } /*public int fill(Inflater inflater, int nativeEndBufSize) throws IOException { synchronized (sharedRaf) { int len = Math.min((int) (endOffset - offset), nativeEndBufSize); int cnt = inflater.setFileInput(sharedRaf.getFD(), offset, nativeEndBufSize); // setFileInput read from the file, so we need to get the OS and RAFStream back // in sync... skip(cnt); return len; } }*/ } /** @hide */ /*public static class ZipInflaterInputStream extends InflaterInputStream { private final ZipEntry entry; private long bytesRead = 0; public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) { super(is, inf, bsize); this.entry = entry; } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { final int i; try { i = super.read(buffer, byteOffset, byteCount); } catch (IOException e) { throw new IOException("Error reading data for " + entry.getName() + " near offset " + bytesRead, e); } if (i == -1) { if (entry.size != bytesRead) { throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs " + entry.size); } } else { bytesRead += i; } return i; } @Override public int available() throws IOException { if (closed) { // Our superclass will throw an exception, but there's a jtreg test that // explicitly checks that the InputStream returned from ZipFile.getInputStream // returns 0 even when closed. return 0; } return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead); } }*/ } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipOutputStream.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; // import libcore.util.CountingOutputStream; // import libcore.util.EmptyArray; import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipInputStream; // import java.nio.charset.StandardCharsets; // import java.util.Arrays; /** * modify by zhangshaowen on 16/6/7. * remove zip64 * const time, modDate * remove entry extra * remove entry comment * * Used to write (compress) data into zip files. * *

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

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

Example

*

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

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

UTF-8 will be used to encode the file comment, entry names and comments. */ public TinkerZipOutputStream(OutputStream os) { this(os, false /* forceZip64 */); } /** * @hide for testing only. */ public TinkerZipOutputStream(OutputStream os, boolean forceZip64) { this(os, forceZip64, 4); } public TinkerZipOutputStream(OutputStream os, boolean forceZip64, int alignBytes) { super(os); this.forceZip64 = forceZip64; this.alignBytes = alignBytes; } /** * Sets the default compression method to be used when a {@code ZipEntry} doesn't * explicitly specify a method. See {@link TinkerZipEntry#setMethod} for more details. */ /*public void setMethod(int method) { if (method != STORED && method != DEFLATED) { throw new IllegalArgumentException("Bad method: " + method); } defaultCompressionMethod = method; }*/ static long writeLongAsUint32(OutputStream os, long i) throws IOException { // Write out the long value as an unsigned int os.write((int) (i & 0xFF)); os.write((int) (i >> 8) & 0xFF); os.write((int) (i >> 16) & 0xFF); os.write((int) (i >> 24) & 0xFF); return i; } static long writeLongAsUint64(OutputStream os, long i) throws IOException { int i1 = (int) i; os.write(i1 & 0xFF); os.write((i1 >> 8) & 0xFF); os.write((i1 >> 16) & 0xFF); os.write((i1 >> 24) & 0xFF); int i2 = (int) (i >> 32); os.write(i2 & 0xFF); os.write((i2 >> 8) & 0xFF); os.write((i2 >> 16) & 0xFF); os.write((i2 >> 24) & 0xFF); return i; } static int writeIntAsUint16(OutputStream os, int i) throws IOException { os.write(i & 0xFF); os.write((i >> 8) & 0xFF); return i; } /** * Closes the current {@code ZipEntry}, if any, and the underlying output * stream. If the stream is already closed this method does nothing. * * @throws IOException * If an error occurs closing the stream. */ @Override public void close() throws IOException { // don't call super.close() because that calls finish() conditionally if (out != null) { finish(); // def.end(); out.close(); out = null; } } /*private void checkAndSetZip64Requirements(ZipEntry entry) { final long totalBytesWritten = getBytesWritten(); final long entriesWritten = entries.size(); currentEntryNeedsZip64 = false; if (forceZip64) { currentEntryNeedsZip64 = true; archiveNeedsZip64EocdRecord = true; return; } // In this particular case, we'll write a zip64 eocd record locator and a zip64 eocd // record but we won't actually need zip64 extended info records for any of the individual // entries (unless they trigger the checks below). if (entriesWritten == 64*1024 - 1) { archiveNeedsZip64EocdRecord = true; } // Check whether we'll need to write out a zip64 extended info record in both the local file header // and the central directory. In addition, we will need a zip64 eocd record locator // and record to mark this archive as zip64. // // TODO: This is an imprecise check. When method != STORED it's possible that the compressed // size will be (slightly) larger than the actual size. How can we improve this ? if (totalBytesWritten > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE)) { currentEntryNeedsZip64 = true; archiveNeedsZip64EocdRecord = true; } }*/ /** * Closes the current {@code ZipEntry}. Any entry terminal data is written * to the underlying stream. * * @throws IOException * If an error occurs closing the entry. */ public void closeEntry() throws IOException { checkOpen(); if (currentEntry == null) { return; } /*if (currentEntry.getMethod() == DEFLATED) { super.finish(); } // Verify values for STORED types if (currentEntry.getMethod() == STORED) { if (crc.getValue() != currentEntry.crc) { throw new ZipException("CRC mismatch"); } if (currentEntry.size != crc.tbytes) { throw new ZipException("Size mismatch"); } }*/ long curOffset = LOCHDR; // Write the DataDescriptor if (currentEntry.getMethod() != STORED) { curOffset += EXTHDR; // Data descriptor signature and CRC are 4 bytes each for both zip and zip64. writeLongAsUint32(out, EXTSIG); /*writeLongAsUint32(out, currentEntry.crc = crc.getValue()); currentEntry.compressedSize = def.getBytesWritten(); currentEntry.size = def.getBytesRead();*/ writeLongAsUint32(out, currentEntry.crc); /*if (currentEntryNeedsZip64) { // We need an additional 8 bytes to store 8 byte compressed / uncompressed // sizes. curOffset += 8; writeLongAsUint64(out, currentEntry.compressedSize); writeLongAsUint64(out, currentEntry.size); } else { writeLongAsUint32(out, currentEntry.compressedSize); writeLongAsUint32(out, currentEntry.size); }*/ writeLongAsUint32(out, currentEntry.compressedSize); writeLongAsUint32(out, currentEntry.size); } // Update the CentralDirectory // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = currentEntry.getMethod() == STORED ? 0 : TinkerZipFile.GPBF_DATA_DESCRIPTOR_FLAG; // Since gingerbread, we always set the UTF-8 flag on individual files if appropriate. // Some tools insist that the central directory have the UTF-8 flag. // http://code.google.com/p/android/issues/detail?id=20214 flags |= TinkerZipFile.GPBF_UTF8_FLAG; writeLongAsUint32(cDir, CENSIG); writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Version this file was made by. writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract. writeIntAsUint16(cDir, flags); writeIntAsUint16(cDir, currentEntry.getMethod()); writeIntAsUint16(cDir, currentEntry.time); writeIntAsUint16(cDir, currentEntry.modDate); // writeLongAsUint32(cDir, crc.getValue()); writeLongAsUint32(cDir, currentEntry.crc); if (currentEntry.getMethod() == DEFLATED) { /*currentEntry.setCompressedSize(def.getBytesWritten()); currentEntry.setSize(def.getBytesRead());*/ curOffset += currentEntry.getCompressedSize(); } else { /*currentEntry.setCompressedSize(crc.tbytes); currentEntry.setSize(crc.tbytes);*/ curOffset += currentEntry.getSize(); } /*if (currentEntryNeedsZip64) { // Refresh the extended info with the compressed size / size before // writing it to the central directory. Zip64.refreshZip64ExtendedInfo(currentEntry); // NOTE: We would've written out the zip64 extended info locator to the entry // extras while constructing the local file header. There's no need to do it again // here. If we do, there will be a size mismatch since we're calculating offsets // based on the *current* size of the extra data and not based on the size // at the point of writing the LFH. writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); } else { writeLongAsUint32(cDir, currentEntry.getCompressedSize()); writeLongAsUint32(cDir, currentEntry.getSize()); }*/ writeLongAsUint32(cDir, currentEntry.getCompressedSize()); writeLongAsUint32(cDir, currentEntry.getSize()); curOffset += writeIntAsUint16(cDir, nameBytes.length); if (currentEntry.extra != null) { curOffset += writeIntAsUint16(cDir, currentEntry.extra.length); } else { writeIntAsUint16(cDir, 0); } writeIntAsUint16(cDir, entryCommentBytes.length); // Comment length. writeIntAsUint16(cDir, 0); // Disk Start writeIntAsUint16(cDir, 0); // Internal File Attributes writeLongAsUint32(cDir, 0); // External File Attributes /*if (currentEntryNeedsZip64) { writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); } else { writeLongAsUint32(cDir, currentEntry.localHeaderRelOffset); }*/ writeLongAsUint32(cDir, currentEntry.localHeaderRelOffset); cDir.write(nameBytes); nameBytes = null; if (currentEntry.extra != null) { cDir.write(currentEntry.extra); } offset += curOffset + padding; padding = 0; if (entryCommentBytes.length > 0) { cDir.write(entryCommentBytes); entryCommentBytes = BYTE; } currentEntry = null; /*crc.reset(); def.reset(); done = false;*/ } /** * Sets the compression level to be used * for writing entry data. */ /*public void setLevel(int level) { if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) { throw new IllegalArgumentException("Bad level: " + level); } compressionLevel = level; }*/ /** * Indicates that all entries have been written to the stream. Any terminal * information is written to the underlying stream. * * @throws IOException * if an error occurs while terminating the stream. */ // @Override public void finish() throws IOException { // TODO: is there a bug here? why not checkOpen? if (out == null) { throw new IOException("Stream is closed"); } if (cDir == null) { return; } if (entries.isEmpty()) { throw new ZipException("No entries"); } if (currentEntry != null) { closeEntry(); } int cdirEntriesSize = cDir.size(); /*if (archiveNeedsZip64EocdRecord) { Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize); }*/ // Write Central Dir End writeLongAsUint32(cDir, ENDSIG); writeIntAsUint16(cDir, 0); // Disk Number writeIntAsUint16(cDir, 0); // Start Disk // Instead of trying to figure out *why* this archive needed a zip64 eocd record, // just delegate all these values to the zip64 eocd record. if (archiveNeedsZip64EocdRecord) { writeIntAsUint16(cDir, 0xFFFF); // Number of entries writeIntAsUint16(cDir, 0xFFFF); // Number of entries writeLongAsUint32(cDir, 0xFFFFFFFF); // Size of central dir writeLongAsUint32(cDir, 0xFFFFFFFF); // Offset of central dir; } else { writeIntAsUint16(cDir, entries.size()); // Number of entries writeIntAsUint16(cDir, entries.size()); // Number of entries writeLongAsUint32(cDir, cdirEntriesSize); // Size of central dir writeLongAsUint32(cDir, offset + padding); // Offset of central dir } writeIntAsUint16(cDir, commentBytes.length); if (commentBytes.length > 0) { cDir.write(commentBytes); } // Write the central directory. cDir.writeTo(out); cDir = null; } private int getPaddingByteCount(TinkerZipEntry entry, long entryFileOffset) { if (entry.getMethod() != ZipEntry.STORED || alignBytes == 0) { return 0; } return (int) ((alignBytes - (entryFileOffset % alignBytes)) % alignBytes); } private void makePaddingToStream(OutputStream os, long padding) throws IOException { if (padding <= 0) { return; } while (padding-- > 0) { os.write(0); } } /** * Writes entry information to the underlying stream. Data associated with * the entry can then be written using {@code write()}. After data is * written {@code closeEntry()} must be called to complete the writing of * the entry to the underlying stream. * * @param ze * the {@code ZipEntry} to store. * @throws IOException * If an error occurs storing the entry. * @see #write */ public void putNextEntry(TinkerZipEntry ze) throws IOException { if (currentEntry != null) { closeEntry(); } // Did this ZipEntry specify a method, or should we use the default? int method = ze.getMethod(); if (method == -1) { method = defaultCompressionMethod; } // If the method is STORED, check that the ZipEntry was configured appropriately. if (method == STORED) { if (ze.getCompressedSize() == -1) { ze.setCompressedSize(ze.getSize()); } else if (ze.getSize() == -1) { ze.setSize(ze.getCompressedSize()); } if (ze.getCrc() == -1) { throw new ZipException("STORED entry missing CRC"); } if (ze.getSize() == -1) { throw new ZipException("STORED entry missing size"); } if (ze.size != ze.compressedSize) { throw new ZipException("STORED entry size/compressed size mismatch"); } } checkOpen(); // checkAndSetZip64Requirements(ze); //zhangshaowen edit here, we just want the same time and modDate ze.comment = null; ze.extra = null; ze.time = TIME_CONST; ze.modDate = MOD_DATE_CONST; nameBytes = ze.name.getBytes(StandardCharsets.UTF_8); checkSizeIsWithinShort("Name", nameBytes); entryCommentBytes = BYTE; if (ze.comment != null) { entryCommentBytes = ze.comment.getBytes(StandardCharsets.UTF_8); // The comment is not written out until the entry is finished, but it is validated here // to fail-fast. checkSizeIsWithinShort("Comment", entryCommentBytes); } // def.setLevel(compressionLevel); ze.setMethod(method); currentEntry = ze; currentEntry.localHeaderRelOffset = offset; entries.add(currentEntry.name); // Local file header. // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = (method == STORED) ? 0 : TinkerZipFile.GPBF_DATA_DESCRIPTOR_FLAG; // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used // modified UTF-8. From Java 7, when using UTF_8 it sets this flag and uses normal UTF-8.) flags |= TinkerZipFile.GPBF_UTF8_FLAG; writeLongAsUint32(out, LOCSIG); // Entry header writeIntAsUint16(out, ZIP_VERSION_2_0); // Minimum version needed to extract. writeIntAsUint16(out, flags); writeIntAsUint16(out, method); // zhangshaowen edit here, we just want the same time and modDate // if (currentEntry.getTime() == -1) { // currentEntry.setTime(System.currentTimeMillis()); // } writeIntAsUint16(out, currentEntry.time); writeIntAsUint16(out, currentEntry.modDate); if (method == STORED) { writeLongAsUint32(out, currentEntry.crc); /*if (currentEntryNeedsZip64) { // NOTE: According to the spec, we're allowed to use these fields under zip64 // as long as the sizes are <= 4G (and omit writing the zip64 extended information header). // // For simplicity, we write the zip64 extended info here even if we only need it // in the central directory (i.e, the case where we're turning on zip64 because the // offset to this entries LFH is > 0xFFFFFFFF). out.write(ZIP64_PLACEHOLDER_BYTES); // compressed size out.write(ZIP64_PLACEHOLDER_BYTES); // uncompressed size } else { writeLongAsUint32(out, currentEntry.size); writeLongAsUint32(out, currentEntry.size); }*/ writeLongAsUint32(out, currentEntry.size); writeLongAsUint32(out, currentEntry.size); } else { writeLongAsUint32(out, 0); writeLongAsUint32(out, 0); writeLongAsUint32(out, 0); } final int nameLength = nameBytes.length; writeIntAsUint16(out, nameLength); final long currDataOffset = offset + LOCHDR + nameLength + (currentEntry.getExtra() != null ? currentEntry.getExtra().length : 0); padding = getPaddingByteCount(currentEntry, currDataOffset); /*if (currentEntryNeedsZip64) { Zip64.insertZip64ExtendedInfoToExtras(currentEntry); }*/ if (currentEntry.extra != null) { writeIntAsUint16(out, currentEntry.extra.length + padding); } else { writeIntAsUint16(out, padding); } out.write(nameBytes); if (currentEntry.extra != null) { out.write(currentEntry.extra); } makePaddingToStream(out, padding); } /** * Sets the comment associated with the file being written. See {@link TinkerZipFile#getComment}. * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes. */ public void setComment(String comment) { if (comment == null) { this.commentBytes = BYTE; return; } byte[] newCommentBytes = comment.getBytes(StandardCharsets.UTF_8); checkSizeIsWithinShort("Comment", newCommentBytes); this.commentBytes = newCommentBytes; } /** * Writes data for the current entry to the underlying stream. * * @throws IOException * If an error occurs writing to the stream */ @Override public void write(byte[] buffer, int offset, int byteCount) throws IOException { Arrays.checkOffsetAndCount(buffer.length, offset, byteCount); if (currentEntry == null) { throw new ZipException("No active entry"); } /*final long totalBytes = crc.tbytes + byteCount; if ((totalBytes > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) && !currentEntryNeedsZip64) { throw new IOException("Zip entry size (" + totalBytes + " bytes) cannot be represented in the zip format (needs Zip64)." + " Set the entry length using ZipEntry#setLength to use Zip64 where necessary."); }*/ if (currentEntry.getMethod() == STORED) { out.write(buffer, offset, byteCount); } else { out.write(buffer, offset, byteCount); } // crc.update(buffer, offset, byteCount); } private void checkOpen() throws IOException { if (cDir == null) { throw new IOException("Stream is closed"); } } private void checkSizeIsWithinShort(String property, byte[] bytes) { if (bytes.length > 0xffff) { throw new IllegalArgumentException(property + " too long in UTF-8:" + bytes.length + " bytes"); } } /*private long getBytesWritten() { // This cast is somewhat messy but less error prone than keeping an // CountingOutputStream reference around in addition to the FilterOutputStream's // out. return ((CountingOutputStream) out).getCount(); }*/ } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * Created by zhangshaowen on 16/8/10. */ public class TinkerZipUtil { private static final int BUFFER_SIZE = 4096; public static void extractTinkerEntry(TinkerZipFile apk, TinkerZipEntry zipEntry, TinkerZipOutputStream outputStream) throws IOException { InputStream in = null; try { in = apk.getInputStream(zipEntry); outputStream.putNextEntry(new TinkerZipEntry(zipEntry)); byte[] buffer = new byte[BUFFER_SIZE]; for (int length = in.read(buffer); length != -1; length = in.read(buffer)) { outputStream.write(buffer, 0, length); } outputStream.closeEntry(); } finally { if (in != null) { in.close(); } } } public static void extractLargeModifyFile(TinkerZipEntry sourceArscEntry, File newFile, long newFileCrc, TinkerZipOutputStream outputStream) throws IOException { TinkerZipEntry newArscZipEntry = new TinkerZipEntry(sourceArscEntry); newArscZipEntry.setMethod(TinkerZipEntry.STORED); newArscZipEntry.setSize(newFile.length()); newArscZipEntry.setCompressedSize(newFile.length()); newArscZipEntry.setCrc(newFileCrc); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(newFile)); outputStream.putNextEntry(new TinkerZipEntry(newArscZipEntry)); byte[] buffer = new byte[BUFFER_SIZE]; for (int length = in.read(buffer); length != -1; length = in.read(buffer)) { outputStream.write(buffer, 0, length); } outputStream.closeEntry(); } finally { if (in != null) { in.close(); } } } public static boolean validateZipEntryName(File destDir, String entryName) { if (entryName == null || entryName.isEmpty()) { return false; } try { final String canonicalDestinationDir = destDir.getCanonicalPath(); final File destEntryFile = new File(destDir, entryName); return destEntryFile.getCanonicalPath().startsWith(canonicalDestinationDir + File.separator); } catch (Throwable ignored) { return false; } } } ================================================ FILE: third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/ZipConstants.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.ziputils.ziputil; /** * modify by zhangshaowen on 16/6/7. * * Do not add constants to this interface! It's implemented by the classes * in this package whose names start "Zip", and the constants are thereby * public API. */ interface ZipConstants { long LOCSIG = 0x4034b50, EXTSIG = 0x8074b50, CENSIG = 0x2014b50, ENDSIG = 0x6054b50; int LOCHDR = 30, EXTHDR = 16, CENHDR = 46, ENDHDR = 22, LOCVER = 4, LOCFLG = 6, LOCHOW = 8, LOCTIM = 10, LOCCRC = 14, LOCSIZ = 18, LOCLEN = 22, LOCNAM = 26, LOCEXT = 28, EXTCRC = 4, EXTSIZ = 8, EXTLEN = 12, CENVEM = 4, CENVER = 6, CENFLG = 8, CENHOW = 10, CENTIM = 12, CENCRC = 16, CENSIZ = 20, CENLEN = 24, CENNAM = 28, CENEXT = 30, CENCOM = 32, CENDSK = 34, CENATT = 36, CENATX = 38, CENOFF = 42, ENDSUB = 8, ENDTOT = 10, ENDSIZ = 12, ENDOFF = 16, ENDCOM = 20; } ================================================ FILE: tinker-android/consumer-proguard.txt ================================================ # Understand the Tinker @Keep annotation. -keep class com.tencent.tinker.anno.Keep -keep @com.tencent.tinker.anno.Keep class * {*;} -keepclasseswithmembers class * { @com.tencent.tinker.anno.Keep ; } -keepclasseswithmembers class * { @com.tencent.tinker.anno.Keep ; } -keepclasseswithmembers class * { @com.tencent.tinker.anno.Keep (...); } -dontwarn android.content.pm.PackageManager$DexModuleRegisterCallback -keep class * extends android.content.pm.PackageManager$DexModuleRegisterCallback { ; ; } -keepclassmembernames class com.tencent.tinker.loader.** { ; ; } -keep,allowobfuscation class com.tencent.tinker.loader.TinkerLoader { private static Guard sProcessGuardRef; } ================================================ FILE: tinker-android/tinker-android-anno/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-anno/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':tinker-android:tinker-android-anno-support') } sourceSets { main { java { srcDir 'src/main/java' } resources { srcDir 'src/main/resources' } } } sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from('build/libs') { include '*.jar' exclude '*javadoc.jar' exclude '*-sources.jar' } into(rootProject.file("buildSdk/android")) } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-anno/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-anno POM_NAME=Tinker Android Anno POM_PACKAGING=jar ================================================ FILE: tinker-android/tinker-android-anno/src/main/resources/META-INF/services/javax.annotation.processing.Processor ================================================ com.tencent.tinker.anno.AnnotationProcessor ================================================ FILE: tinker-android/tinker-android-anno/src/main/resources/TinkerAnnoApplication.tmpl ================================================ package %PACKAGE%; import com.tencent.tinker.loader.app.TinkerApplication; /** * * Generated application for tinker life cycle * */ public class %APPLICATION% extends TinkerApplication { public %APPLICATION%() { super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%, %TINKER_USE_DLC%, %TINKER_USE_INTERPRET_MODE_ON_SUPPORTED_32BIT_SYSTEM%); } } ================================================ FILE: tinker-android/tinker-android-anno/src/test/java/com/tencent/tinker/anno/test/TestLifeCycle.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.anno.test; //import com.tencent.tinker.anno.DefaultLifeCycle; // ///** // * Test LifeCycle // * // * Created by zhaoyuan on 16/4/1. // */ //@DefaultLifeCycle(application = ".TestApplication", flags = 0x10, loaderClass="com.tencent.tinker.loader.TinkerLoader") //public class TestLifeCycle { // //} ================================================ FILE: tinker-android/tinker-android-anno-support/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-anno-support/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-anno-support/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-anno-support POM_NAME=Tinker Android Annotation Support POM_PACKAGING=jar ================================================ FILE: tinker-android/tinker-android-anno-support/src/main/java/com/tencent/tinker/anno/AnnotationProcessor.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.anno; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Writer; import java.util.LinkedHashSet; import java.util.Scanner; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** * Tinker Annotations Processor * * Created by zhaoyuan on 16/3/31. */ @SupportedSourceVersion(SourceVersion.RELEASE_7) public class AnnotationProcessor extends AbstractProcessor { private static final String SUFFIX = "$$DefaultLifeCycle"; private static final String APPLICATION_TEMPLATE_PATH = "/TinkerAnnoApplication.tmpl"; @Override public Set getSupportedAnnotationTypes() { final Set supportedAnnotationTypes = new LinkedHashSet<>(); supportedAnnotationTypes.add(DefaultLifeCycle.class.getName()); return supportedAnnotationTypes; } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { processDefaultLifeCycle(roundEnv.getElementsAnnotatedWith(DefaultLifeCycle.class)); return true; } private void processDefaultLifeCycle(Set elements) { // DefaultLifeCycle for (Element e : elements) { DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class); String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString(); String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.')); lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1); String applicationClassName = ca.application(); if (applicationClassName.startsWith(".")) { applicationClassName = lifeCyclePackageName + applicationClassName; } String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.')); applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1); String loaderClassName = ca.loaderClass(); if (loaderClassName.startsWith(".")) { loaderClassName = lifeCyclePackageName + loaderClassName; } final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH); final Scanner scanner = new Scanner(is); final String template = scanner.useDelimiter("\\A").next(); final String fileContent = template .replaceAll("%PACKAGE%", applicationPackageName) .replaceAll("%APPLICATION%", applicationClassName) .replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName) .replaceAll("%TINKER_FLAGS%", "" + ca.flags()) .replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName) .replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag()) .replaceAll("%TINKER_USE_DLC%", "" + ca.useDelegateLastClassLoader()) .replaceAll("%TINKER_USE_INTERPRET_MODE_ON_SUPPORTED_32BIT_SYSTEM%", "" + ca.useInterpretModeOnSupported32BitSystem()); try { JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName); processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri()); Writer writer = fileObject.openWriter(); try { PrintWriter pw = new PrintWriter(writer); pw.print(fileContent); pw.flush(); } finally { writer.close(); } } catch (IOException x) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString()); } } } } ================================================ FILE: tinker-android/tinker-android-anno-support/src/main/java/com/tencent/tinker/anno/DefaultLifeCycle.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.anno; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotations * * Created by zhaoyuan on 16/3/31. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) @Inherited public @interface DefaultLifeCycle { String application(); String loaderClass() default "com.tencent.tinker.loader.TinkerLoader"; int flags(); boolean loadVerifyFlag() default false; boolean useDelegateLastClassLoader() default false; boolean useInterpretModeOnSupported32BitSystem() default false; } ================================================ FILE: tinker-android/tinker-android-anno-support/src/main/java/com/tencent/tinker/anno/Keep.java ================================================ package com.tencent.tinker.anno; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by tangyinsheng on 2020-03-27. */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD}) public @interface Keep { } ================================================ FILE: tinker-android/tinker-android-lib/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-lib/build.gradle ================================================ apply plugin: 'com.android.library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion buildConfigField "String", "TINKER_VERSION", "\"${rootProject.ext.VERSION_NAME}\"" manifestPlaceholders = [TINKER_VERSION: "${rootProject.ext.VERSION_NAME}"] consumerProguardFiles file('../consumer-proguard.txt') } lintOptions { disable 'LongLogTag' } compileOptions { sourceCompatibility rootProject.ext.javaVersion targetCompatibility rootProject.ext.javaVersion } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' implementation project(':tinker-android:tinker-android-anno-support') api project(':tinker-android:tinker-android-loader') api project(':tinker-commons') } task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from("$buildDir/outputs/aar/") { include "${project.getName()}-release.aar" } into(rootProject.file("buildSdk/android/")) rename { String fileName -> fileName.replace("release", "${version}") } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-lib/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-lib POM_NAME=Tinker Android Lib ================================================ FILE: tinker-android/tinker-android-lib/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: tinker-android/tinker-android-lib/src/androidTest/java/com/tencent/tinker/lib/patch/ApplicationTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/AndroidManifest.xml ================================================ ================================================ FILE: tinker-android/tinker-android-lib/src/main/aidl/com/tencent/tinker/lib/IForeService.aidl ================================================ // IForeService.aidl package com.tencent.tinker.lib; interface IForeService { void startme(); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/entry/ApplicationLifeCycle.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; /** * Created by zhangshaowen on 16/3/8. */ import android.app.Application; import android.content.Context; import android.content.res.Configuration; /** * This interface is used to delegate calls from main Application object. * * Implementations of this interface must have a one-argument constructor that takes * an argument of type {@link Application}. */ public interface ApplicationLifeCycle { /** * Same as {@link Application#onCreate()}. */ void onCreate(); /** * Same as {@link Application#onLowMemory()}. */ void onLowMemory(); /** * Same as {@link Application#onTrimMemory(int level)}. * @param level */ void onTrimMemory(int level); /** * Same as {@link Application#onTerminate()}. */ void onTerminate(); /** * Same as {@link Application#onConfigurationChanged(Configuration newconfig)}. */ void onConfigurationChanged(Configuration newConfig); /** * Same as {@link Application#attachBaseContext(Context context)}. */ void onBaseContextAttached(Context base); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/entry/ApplicationLike.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import com.tencent.tinker.anno.Keep; /** * Created by zhangshaowen on 16/7/28. */ @Keep public abstract class ApplicationLike implements ApplicationLifeCycle { private final Application application; private final Intent tinkerResultIntent; private final long applicationStartElapsedTime; private final long applicationStartMillisTime; private final int tinkerFlags; private final boolean tinkerLoadVerifyFlag; public ApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { this.application = application; this.tinkerFlags = tinkerFlags; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.applicationStartElapsedTime = applicationStartElapsedTime; this.applicationStartMillisTime = applicationStartMillisTime; this.tinkerResultIntent = tinkerResultIntent; } public Application getApplication() { return application; } public final Intent getTinkerResultIntent() { return tinkerResultIntent; } public final int getTinkerFlags() { return tinkerFlags; } public final boolean getTinkerLoadVerifyFlag() { return tinkerLoadVerifyFlag; } public long getApplicationStartElapsedTime() { return applicationStartElapsedTime; } public long getApplicationStartMillisTime() { return applicationStartMillisTime; } @Override public void onCreate() { } @Override public void onLowMemory() { } @Override public void onTrimMemory(int level) { } @Override public void onTerminate() { } @Override public void onConfigurationChanged(Configuration newConfig) { } @Override public void onBaseContextAttached(Context base) { } //some get methods that may be overwrite @Keep public Resources getResources(Resources resources) { return resources; } @Keep public ClassLoader getClassLoader(ClassLoader classLoader) { return classLoader; } @Keep public AssetManager getAssets(AssetManager assetManager) { return assetManager; } @Keep public Object getSystemService(String name, Object service) { return service; } @Keep public Context getBaseContext(Context base) { return base; } @Keep public Theme getTheme(Theme theme) { return theme; } @Keep public int mzNightModeUseOf() { // Return 1 for default according to MeiZu's announcement. return 1; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/entry/DefaultApplicationLike.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; /** * Created by zhangshaowen on 16/3/8. */ import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.util.Log; import com.tencent.tinker.anno.Keep; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Empty implementation of {@link ApplicationLike}. */ @Keep public class DefaultApplicationLike extends ApplicationLike { private static final String TAG = "Tinker.DefaultAppLike"; public DefaultApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @Override public void onCreate() { ShareTinkerLog.d(TAG, "onCreate"); } @Override public void onLowMemory() { ShareTinkerLog.d(TAG, "onLowMemory"); } @Override public void onTrimMemory(int level) { ShareTinkerLog.d(TAG, "onTrimMemory level:" + level); } @Override public void onTerminate() { ShareTinkerLog.d(TAG, "onTerminate"); } @Override public void onConfigurationChanged(Configuration newConfig) { ShareTinkerLog.d(TAG, "onConfigurationChanged:" + newConfig.toString()); } @Override public void onBaseContextAttached(Context base) { ShareTinkerLog.d(TAG, "onBaseContextAttached:"); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/entry/TinkerApplicationInlineFence.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.os.Handler; import android.os.Message; import com.tencent.tinker.anno.Keep; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_ASSETS; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_BASE_CONTEXT; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_CLASSLOADER; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_RESOURCES; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_SYSTEM_SERVICE; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_GET_THEME; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_MZ_NIGHTMODE_USE_OF; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_BASE_CONTEXT_ATTACHED; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_CONFIGURATION_CHANGED; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_CREATE; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_LOW_MEMORY; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_TERMINATE; import static com.tencent.tinker.loader.app.TinkerInlineFenceAction.ACTION_ON_TRIM_MEMORY; /** * Created by tangyinsheng on 2019-11-05. */ @Keep public final class TinkerApplicationInlineFence extends Handler { private final ApplicationLike mAppLike; public TinkerApplicationInlineFence(ApplicationLike appLike) { mAppLike = appLike; } @Override public void handleMessage(Message msg) { handleMessage_$noinline$(msg); } private void handleMessage_$noinline$(Message msg) { try { dummyThrowExceptionMethod(); } finally { handleMessageImpl(msg); } } @Override public void dispatchMessage(Message msg) { // Any requests come from dispatchMessage are unexpected. Ignore them should be ok. } private void handleMessageImpl(Message msg) { switch (msg.what) { case ACTION_ON_BASE_CONTEXT_ATTACHED: { mAppLike.onBaseContextAttached((Context) msg.obj); break; } case ACTION_ON_CREATE: { mAppLike.onCreate(); break; } case ACTION_ON_CONFIGURATION_CHANGED: { mAppLike.onConfigurationChanged((Configuration) msg.obj); break; } case ACTION_ON_TRIM_MEMORY: { mAppLike.onTrimMemory((Integer) msg.obj); break; } case ACTION_ON_LOW_MEMORY: { mAppLike.onLowMemory(); break; } case ACTION_ON_TERMINATE: { mAppLike.onTerminate(); break; } case ACTION_GET_CLASSLOADER: { msg.obj = mAppLike.getClassLoader((ClassLoader) msg.obj); break; } case ACTION_GET_BASE_CONTEXT: { msg.obj = mAppLike.getBaseContext((Context) msg.obj); break; } case ACTION_GET_ASSETS: { msg.obj = mAppLike.getAssets((AssetManager) msg.obj); break; } case ACTION_GET_RESOURCES: { msg.obj = mAppLike.getResources((Resources) msg.obj); break; } case ACTION_GET_SYSTEM_SERVICE: { final Object[] params = (Object[]) msg.obj; msg.obj = mAppLike.getSystemService((String) params[0], params[1]); break; } case ACTION_GET_THEME: { msg.obj = mAppLike.getTheme((Theme) msg.obj); break; } case ACTION_MZ_NIGHTMODE_USE_OF: { msg.obj = mAppLike.mzNightModeUseOf(); break; } default: { throw new IllegalStateException("Should not be here."); } } } private static void dummyThrowExceptionMethod() { if (TinkerApplicationInlineFence.class.isPrimitive()) { throw new RuntimeException(); } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/filepatch/AbstractFilePatch.java ================================================ package com.tencent.tinker.lib.filepatch; import java.io.File; import java.io.IOException; import java.io.InputStream; public abstract class AbstractFilePatch { public abstract int patchFast(InputStream oldInputStream, InputStream diffInputStream, File newFile) throws IOException; } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/filepatch/BsFilePatch.java ================================================ package com.tencent.tinker.lib.filepatch; import com.tencent.tinker.bsdiff.BSPatch; import java.io.File; import java.io.IOException; import java.io.InputStream; public class BsFilePatch extends AbstractFilePatch{ @Override public int patchFast(InputStream oldInputStream, InputStream diffInputStream, File newFile) throws IOException { return BSPatch.patchFast(oldInputStream, diffInputStream, newFile); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/filepatch/FilePatchFactory.java ================================================ package com.tencent.tinker.lib.filepatch; import android.content.Context; import android.util.Log; import com.tencent.tinker.lib.tinker.Tinker; public class FilePatchFactory { private static final String TAG = "MicroMsg.FilePatchFactory"; public static AbstractFilePatch getFilePatcher(Context context, boolean useCustomPatcher) { if (Tinker.with(context).getCustomPatcher() == null || !useCustomPatcher) { Log.i(TAG, "BsFilePatch"); return new BsFilePatch(); } Log.i(TAG, "CustomPatcher"); return Tinker.with(context).getCustomPatcher(); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/library/TinkerLoadLibrary.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.library; import android.content.Context; import android.os.Build; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerApplicationHelper; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.lib.tinker.TinkerLoadResult; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created by zhangshaowen on 17/1/5. * Thanks for Android Fragmentation */ public class TinkerLoadLibrary { private static final String TAG = "Tinker.LoadLibrary"; /** * you can use TinkerInstaller.loadLibrary replace your System.loadLibrary for auto update library! * only support auto load lib/armeabi library from patch. * for other library in lib/* or assets, * you can load through {@code TinkerInstaller#loadLibraryFromTinker} */ public static void loadArmLibrary(Context context, String libName) { if (libName == null || libName.isEmpty() || context == null) { throw new TinkerRuntimeException("libName or context is null!"); } Tinker tinker = Tinker.with(context); if (tinker.isEnabledForNativeLib()) { if (TinkerLoadLibrary.loadLibraryFromTinker(context, "lib/armeabi", libName)) { return; } } System.loadLibrary(libName); } /** * The same as {@link #loadArmLibrary(Context, String)} but it can be called before * calling {@link TinkerInstaller#install} * @param appLike * @param libName */ public static void loadArmLibraryWithoutTinkerInstalled(ApplicationLike appLike, String libName) { if (libName == null || libName.isEmpty() || appLike == null) { throw new TinkerRuntimeException("libName or appLike is null!"); } if (TinkerApplicationHelper.isTinkerEnableForNativeLib(appLike)) { if (TinkerApplicationHelper.loadLibraryFromTinker(appLike, "lib/armeabi", libName)) { return; } } System.loadLibrary(libName); } /** * you can use TinkerInstaller.loadArmV7Library replace your System.loadLibrary for auto update library! * only support auto load lib/armeabi-v7a library from patch. * for other library in lib/* or assets, * you can load through {@code TinkerInstaller#loadLibraryFromTinker} */ public static void loadArmV7Library(Context context, String libName) { if (libName == null || libName.isEmpty() || context == null) { throw new TinkerRuntimeException("libName or context is null!"); } Tinker tinker = Tinker.with(context); if (tinker.isEnabledForNativeLib()) { if (TinkerLoadLibrary.loadLibraryFromTinker(context, "lib/armeabi-v7a", libName)) { return; } } System.loadLibrary(libName); } /** * The same as {@link #loadArmV7Library(Context, String)} but it can be called before * calling {@link TinkerInstaller#install} * @param appLike * @param libName */ public static void loadArmV7LibraryWithoutTinkerInstalled(ApplicationLike appLike, String libName) { if (libName == null || libName.isEmpty() || appLike == null) { throw new TinkerRuntimeException("libName or appLike is null!"); } if (TinkerApplicationHelper.isTinkerEnableForNativeLib(appLike)) { if (TinkerApplicationHelper.loadLibraryFromTinker(appLike, "lib/armeabi-v7a", libName)) { return; } } System.loadLibrary(libName); } /** * sample usage for native library * * @param context * @param relativePath such as lib/armeabi * @param libName for the lib libTest.so, you can pass Test or libTest, or libTest.so * @return boolean * @throws UnsatisfiedLinkError */ public static boolean loadLibraryFromTinker(Context context, String relativePath, String libName) throws UnsatisfiedLinkError { final Tinker tinker = Tinker.with(context); libName = libName.startsWith("lib") ? libName : "lib" + libName; libName = libName.endsWith(".so") ? libName : libName + ".so"; String relativeLibPath = relativePath + "/" + libName; //TODO we should add cpu abi, and the real path later if (tinker.isEnabledForNativeLib() && tinker.isTinkerLoaded()) { TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent(); if (loadResult.libs == null) { return false; } for (String name : loadResult.libs.keySet()) { if (!name.equals(relativeLibPath)) { continue; } String patchLibraryPath = loadResult.libraryDirectory + "/" + name; File library = new File(patchLibraryPath); if (!library.exists()) { continue; } //whether we check md5 when load boolean verifyMd5 = tinker.isTinkerLoadVerify(); if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadResult.libs.get(name))) { tinker.getLoadReporter().onLoadFileMd5Mismatch(library, ShareConstants.TYPE_LIBRARY); } else { System.load(patchLibraryPath); ShareTinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath); return true; } } } return false; } /** * you can reflect your current abi to classloader library path * as you don't need to use load*Library method above * @param context * @param currentABI */ public static boolean installNavitveLibraryABI(Context context, String currentABI) { Tinker tinker = Tinker.with(context); if (!tinker.isTinkerLoaded()) { ShareTinkerLog.i(TAG, "tinker is not loaded, just return"); return false; } TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent(); if (loadResult.libs == null) { ShareTinkerLog.i(TAG, "tinker libs is null, just return"); return false; } File soDir = new File(loadResult.libraryDirectory, "lib/" + currentABI); if (!soDir.exists()) { ShareTinkerLog.e(TAG, "current libraryABI folder is not exist, path: %s", soDir.getPath()); return false; } ClassLoader classLoader = context.getClassLoader(); if (classLoader == null) { ShareTinkerLog.e(TAG, "classloader is null"); return false; } ShareTinkerLog.i(TAG, "before hack classloader:" + classLoader.toString()); try { installNativeLibraryPath(classLoader, soDir); return true; } catch (Throwable throwable) { ShareTinkerLog.e(TAG, "installNativeLibraryPath fail:" + throwable); return false; } finally { ShareTinkerLog.i(TAG, "after hack classloader:" + classLoader.toString()); } } /** * The same as {@link #installNavitveLibraryABI(Context, String)} but it can be called before * calling {@link TinkerInstaller#install} * @param appLike * @param currentABI * @return */ public static boolean installNativeLibraryABIWithoutTinkerInstalled(ApplicationLike appLike, String currentABI) { if (!TinkerApplicationHelper.isTinkerLoadSuccess(appLike)) { ShareTinkerLog.e(TAG, "no loaded patch, skip installation."); return false; } final String currentVersion = TinkerApplicationHelper.getCurrentVersion(appLike); if (ShareTinkerInternals.isNullOrNil(currentVersion)) { ShareTinkerLog.e(TAG, "failed to get current patch version."); return false; } final File patchDirectory = SharePatchFileUtil.getPatchDirectory(appLike.getApplication()); if (patchDirectory == null) { ShareTinkerLog.e(TAG, "failed to get current patch directory."); return false; } File patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + SharePatchFileUtil.getPatchVersionDirectory(currentVersion)); File libPath = new File(patchVersionDirectory.getAbsolutePath() + "/lib/lib/" + currentABI); if (!libPath.exists()) { ShareTinkerLog.e(TAG, "tinker lib path [%s] is not exists.", libPath); return false; } final ClassLoader classLoader = appLike.getApplication().getClassLoader(); if (classLoader == null) { ShareTinkerLog.e(TAG, "classloader is null"); return false; } else { ShareTinkerLog.i(TAG, "before hack classloader:" + classLoader.toString()); try { final Method installNativeLibraryPathMethod = TinkerLoadLibrary.class.getDeclaredMethod("installNativeLibraryPath", ClassLoader.class, File.class); installNativeLibraryPathMethod.setAccessible(true); installNativeLibraryPathMethod.invoke(null, classLoader, libPath); return true; } catch (Throwable thr) { ShareTinkerLog.e(TAG, "installNativeLibraryPath fail:" + libPath + ", thr: " + thr); return false; } finally { ShareTinkerLog.i(TAG, "after hack classloader:" + classLoader.toString()); } } } /** * All version of install logic obey the following strategies: * 1. If path of {@code folder} is not injected into the classloader, inject it to the * beginning of pathList in the classloader. * * 2. Otherwise remove path of {@code folder} first, then re-inject it to the * beginning of pathList in the classloader. */ private static void installNativeLibraryPath(ClassLoader classLoader, File folder) throws Throwable { if (folder == null || !folder.exists()) { ShareTinkerLog.e(TAG, "installNativeLibraryPath, folder %s is illegal", folder); return; } // android o sdk_int 26 // for android o preview sdk_int 25 if ((Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0) || Build.VERSION.SDK_INT > 25) { try { V25.install(classLoader, folder); } catch (Throwable throwable) { // install fail, try to treat it as v23 // some preview N version may go here ShareTinkerLog.e(TAG, "installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23", Build.VERSION.SDK_INT, throwable.getMessage()); V23.install(classLoader, folder); } } else if (Build.VERSION.SDK_INT >= 23) { try { V23.install(classLoader, folder); } catch (Throwable throwable) { // install fail, try to treat it as v14 ShareTinkerLog.e(TAG, "installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14", Build.VERSION.SDK_INT, throwable.getMessage()); V14.install(classLoader, folder); } } else if (Build.VERSION.SDK_INT >= 14) { V14.install(classLoader, folder); } else { V4.install(classLoader, folder); } } private static final class V4 { private static void install(ClassLoader classLoader, File folder) throws Throwable { String addPath = folder.getPath(); Field pathField = ShareReflectUtil.findField(classLoader, "libPath"); final String origLibPaths = (String) pathField.get(classLoader); final String[] origLibPathSplit = origLibPaths.split(":"); final StringBuilder newLibPaths = new StringBuilder(addPath); for (String origLibPath : origLibPathSplit) { if (origLibPath == null || addPath.equals(origLibPath)) { continue; } newLibPaths.append(':').append(origLibPath); } pathField.set(classLoader, newLibPaths.toString()); final Field libraryPathElementsFiled = ShareReflectUtil.findField(classLoader, "libraryPathElements"); final List libraryPathElements = (List) libraryPathElementsFiled.get(classLoader); final Iterator libPathElementIt = libraryPathElements.iterator(); while (libPathElementIt.hasNext()) { final String libPath = libPathElementIt.next(); if (addPath.equals(libPath)) { libPathElementIt.remove(); break; } } libraryPathElements.add(0, addPath); libraryPathElementsFiled.set(classLoader, libraryPathElements); } } private static final class V14 { private static void install(ClassLoader classLoader, File folder) throws Throwable { final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList"); final Object dexPathList = pathListField.get(classLoader); final Field nativeLibDirField = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories"); final File[] origNativeLibDirs = (File[]) nativeLibDirField.get(dexPathList); final List newNativeLibDirList = new ArrayList<>(origNativeLibDirs.length + 1); newNativeLibDirList.add(folder); for (File origNativeLibDir : origNativeLibDirs) { if (!folder.equals(origNativeLibDir)) { newNativeLibDirList.add(origNativeLibDir); } } nativeLibDirField.set(dexPathList, newNativeLibDirList.toArray(new File[0])); } } private static final class V23 { private static void install(ClassLoader classLoader, File folder) throws Throwable { final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList"); final Object dexPathList = pathListField.get(classLoader); final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories"); List origLibDirs = (List) nativeLibraryDirectories.get(dexPathList); if (origLibDirs == null) { origLibDirs = new ArrayList<>(2); } final Iterator libDirIt = origLibDirs.iterator(); while (libDirIt.hasNext()) { final File libDir = libDirIt.next(); if (folder.equals(libDir)) { libDirIt.remove(); break; } } origLibDirs.add(0, folder); final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories"); List origSystemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList); if (origSystemLibDirs == null) { origSystemLibDirs = new ArrayList<>(2); } final List newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1); newLibDirs.addAll(origLibDirs); newLibDirs.addAll(origSystemLibDirs); final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class, List.class); final ArrayList suppressedExceptions = new ArrayList<>(); final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions); final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements"); nativeLibraryPathElements.set(dexPathList, elements); } } private static final class V25 { private static void install(ClassLoader classLoader, File folder) throws Throwable { final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList"); final Object dexPathList = pathListField.get(classLoader); final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories"); List origLibDirs = (List) nativeLibraryDirectories.get(dexPathList); if (origLibDirs == null) { origLibDirs = new ArrayList<>(2); } final Iterator libDirIt = origLibDirs.iterator(); while (libDirIt.hasNext()) { final File libDir = libDirIt.next(); if (folder.equals(libDir)) { libDirIt.remove(); break; } } origLibDirs.add(0, folder); final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories"); List origSystemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList); if (origSystemLibDirs == null) { origSystemLibDirs = new ArrayList<>(2); } final List newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1); newLibDirs.addAll(origLibDirs); newLibDirs.addAll(origSystemLibDirs); final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class); final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs); final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements"); nativeLibraryPathElements.set(dexPathList, elements); } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.listener; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.text.TextUtils; import com.tencent.tinker.lib.service.TinkerPatchForeService; import com.tencent.tinker.lib.service.TinkerPatchService; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerLoadResult; import com.tencent.tinker.lib.util.TinkerServiceInternals; import com.tencent.tinker.lib.util.UpgradePatchRetry; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import static android.content.Context.BIND_AUTO_CREATE; /** * Created by zhangshaowen on 16/3/14. */ public class DefaultPatchListener implements PatchListener { protected final Context context; private ServiceConnection connection; public DefaultPatchListener(Context context) { this.context = context; } /** * when we receive a patch, what would we do? * you can overwrite it * * @param path * @return */ @Override public int onPatchReceived(String path) { return checkPackageAndRunPatchService(path, false); } /** * Check patch package then start patch service to generate patched artifacts. * @param path * Path to your patch package. * @param useEmergencyMode * true for using emergency mode, otherwise false. * * By using emergency mode, dex2oat triggering procedure will be done asynchronously on Android Q and newer * system to save costs. If your app lives too short to wait for generating patch artifacts, this mode should * help. **However, the performance of your patched app will become terribly worse since odex of patched dex(es) * may not be generated before loading patched artifacts in this mode.** */ protected int checkPackageAndRunPatchService(String path, boolean useEmergencyMode) { final File patchFile = new File(path); final String patchMD5 = SharePatchFileUtil.getMD5(patchFile); final int returnCode = patchCheck(path, patchMD5); if (returnCode == ShareConstants.ERROR_PATCH_OK) { runForgService(); TinkerPatchService.runPatchService(context, path, useEmergencyMode); } else { Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode); } return returnCode; } private void runForgService() { try { connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { if (context != null && connection != null) { try { //Tinker在完成补丁后会尝试kill掉patch进程,如果不unbind会导致patch进程重启 context.unbindService(connection); } catch (Throwable ignored) { // Ignored. } } } @Override public void onBindingDied(ComponentName name) { } }; Intent innerForgIntent = new Intent(context, TinkerPatchForeService.class); context.bindService(innerForgIntent, connection, BIND_AUTO_CREATE); } catch (Throwable ex) { //ignore forground service start error } } protected int patchCheck(String path, String patchMd5) { final Tinker manager = Tinker.with(context); //check SharePreferences also if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) { return ShareConstants.ERROR_PATCH_DISABLE; } if (TextUtils.isEmpty(patchMd5)) { return ShareConstants.ERROR_PATCH_NOTEXIST; } final File file = new File(path); if (!SharePatchFileUtil.isLegalFile(file)) { return ShareConstants.ERROR_PATCH_NOTEXIST; } //patch service can not send request if (manager.isPatchProcess()) { return ShareConstants.ERROR_PATCH_INSERVICE; } //if the patch service is running, pending if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) { return ShareConstants.ERROR_PATCH_RUNNING; } if (ShareTinkerInternals.isVmJit()) { return ShareConstants.ERROR_PATCH_JIT; } final TinkerLoadResult loadResult = manager.getTinkerLoadResultIfPresent(); // only call repair on main process final boolean repairOptNeeded = manager.isMainProcess() && loadResult != null && loadResult.useInterpretMode; if (!repairOptNeeded) { // Hit if we have already applied patch but main process did not restart. final String patchDirectory = manager.getPatchDirectory().getAbsolutePath(); File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory); File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory); try { final SharePatchInfo currInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); if (currInfo != null && !ShareTinkerInternals.isNullOrNil(currInfo.newVersion) && !currInfo.newVersion.equals(currInfo.versionToRemove)) { if (patchMd5.equals(currInfo.newVersion)) { return ShareConstants.ERROR_PATCH_ALREADY_APPLY; } } } catch (Throwable ignored) { // Ignored. } } if (!UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)) { return ShareConstants.ERROR_PATCH_RETRY_COUNT_LIMIT; } return ShareConstants.ERROR_PATCH_OK; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/PatchListener.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.listener; /** * Created by zhangshaowen on 16/3/14. */ public interface PatchListener { int onPatchReceived(String path); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import com.tencent.tinker.lib.service.PatchResult; /** * Created by zhangshaowen on 16/3/15. */ public abstract class AbstractPatch { public abstract boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ArkHotDiffPatchInternal.java ================================================ /* * Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.lib.patch; import android.content.Context; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareArkHotDiffPatchInfo; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class ArkHotDiffPatchInternal extends BasePatchInternal { private static final String TAG = "Tinker.ArkHotDiffPatchInternal"; private static ArrayList arkPatchList = new ArrayList<>(); protected static boolean tryRecoverArkHotLibrary(Tinker manager, ShareSecurityCheck checker, Context context, String patchVersionDirectory, File patchFile) { String arkHotMeta = checker.getMetaContentMap().get(ARKHOT_META_FILE); if (arkHotMeta == null) { return true; } patchArkHotLibraryExtract(context, patchVersionDirectory, arkHotMeta, patchFile); return true; } private static boolean extractArkHotLibrary(Context context, String dir, File patchFile, int type) { Tinker manager = Tinker.with(context); ZipFile patch = null; try { patch = new ZipFile(patchFile); for (ShareArkHotDiffPatchInfo info : arkPatchList) { final String path = info.path; final String patchRealPath; if (path.equals("")) { patchRealPath = info.name; } else { patchRealPath = path + "/" + info.name; } final String md5 = info.patchMd5; if (!SharePatchFileUtil.checkIfMd5Valid(md5)) { manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } File extractedFile = new File(dir + info.name); if (extractedFile.exists()) { if (md5.equals(SharePatchFileUtil.getMD5(extractedFile))) { continue; } else { extractedFile.delete(); } } else { extractedFile.getParentFile().mkdirs(); } ZipEntry patchFileEntry = patch.getEntry(patchRealPath); if (!extract(patch, patchFileEntry, extractedFile, md5, false)) { manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); return false; } } } catch (IOException e) { throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); } finally { SharePatchFileUtil.closeZip(patch); } return true; } private static boolean patchArkHotLibraryExtract(Context context, String patchVersionDirectory, String meta, File patchFile) { String dir = patchVersionDirectory + "/" + ShareConstants.ARKHOTFIX_PATH + "/"; arkPatchList.clear(); ShareArkHotDiffPatchInfo.parseDiffPatchInfo(meta, arkPatchList); if (!extractArkHotLibrary(context, dir, patchFile, TYPE_ARKHOT_SO)) { return false; } return true; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/BasePatchInternal.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 16/4/12. */ public class BasePatchInternal { protected static final String TAG = "Tinker.BasePatchInternal"; protected static final String DEX_PATH = ShareConstants.DEX_PATH; protected static final String SO_PATH = ShareConstants.SO_PATH; protected static final String DEX_OPTIMIZE_PATH = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; protected static final int MAX_EXTRACT_ATTEMPTS = ShareConstants.MAX_EXTRACT_ATTEMPTS; protected static final String DEX_META_FILE = ShareConstants.DEX_META_FILE; protected static final String SO_META_FILE = ShareConstants.SO_META_FILE; protected static final String RES_META_FILE = ShareConstants.RES_META_FILE; protected static final String ARKHOT_META_FILE = ShareConstants.ARKHOT_META_FILE; protected static final int TYPE_DEX = ShareConstants.TYPE_DEX; protected static final int TYPE_LIBRARY = ShareConstants.TYPE_LIBRARY; protected static final int TYPE_RESOURCE = ShareConstants.TYPE_RESOURCE; protected static final int TYPE_CLASS_N_DEX = ShareConstants.TYPE_CLASS_N_DEX; protected static final int TYPE_ARKHOT_SO = ShareConstants.TYPE_ARKHOT_SO; public static boolean extract(ZipFile zipFile, ZipEntry entryFile, File extractTo, String targetMd5, boolean isDex) throws IOException { int numAttempts = 0; boolean isExtractionSuccessful = false; while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { numAttempts++; InputStream is = null; OutputStream os = null; ShareTinkerLog.i(TAG, "try Extracting " + extractTo.getPath()); try { if (extractTo.exists()) { extractTo.delete(); } is = new BufferedInputStream(zipFile.getInputStream(entryFile)); os = new BufferedOutputStream(new FileOutputStream(extractTo)); if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) { extractTo.setReadOnly(); } byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; int length = 0; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } } finally { IOHelper.closeQuietly(os); IOHelper.closeQuietly(is); } if (targetMd5 != null) { if (isDex) { isExtractionSuccessful = SharePatchFileUtil.verifyDexFileMd5(extractTo, targetMd5); } else { isExtractionSuccessful = SharePatchFileUtil.verifyFileMd5(extractTo, targetMd5); } } else { // treat it as true isExtractionSuccessful = true; } ShareTinkerLog.i(TAG, "isExtractionSuccessful: %b", isExtractionSuccessful); if (!isExtractionSuccessful) { final boolean succ = extractTo.delete(); if (!succ || extractTo.exists()) { ShareTinkerLog.e(TAG, "Failed to delete corrupted dex " + extractTo.getPath()); } } } return isExtractionSuccessful; } public static int getMetaCorruptedCode(int type) { if (type == TYPE_DEX) { return ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED; } else if (type == TYPE_LIBRARY) { return ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED; } else if (type == TYPE_RESOURCE) { return ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.SystemClock; import com.tencent.tinker.commons.dexpatcher.DexPatchApplier; import com.tencent.tinker.commons.util.DigestUtil; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.lib.service.PatchResult; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.TinkerDexOptimizer; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareDexDiffPatchInfo; import com.tencent.tinker.loader.shareutil.ShareElfFile; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.ziputils.ziputil.AlignedZipOutputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Vector; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; /** * Created by zhangshaowen on 16/4/12. */ public class DexDiffPatchInternal extends BasePatchInternal { protected static final String TAG = "Tinker.DexDiffPatchInternal"; protected static final int WAIT_ASYN_OAT_TIME = 10 * 1000; protected static final int MAX_WAIT_COUNT = 120; private static ArrayList optFiles = new ArrayList<>(); private static ArrayList patchList = new ArrayList<>(); private static HashMap classNDexInfo = new HashMap<>(); private static boolean isVmArt = ShareTinkerInternals.isVmArt(); protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context, String patchVersionDirectory, File patchFile, boolean useEmergencyMode, PatchResult patchResult) { if (!manager.isEnabledForDex()) { ShareTinkerLog.w(TAG, "patch recover, dex is not enabled"); return true; } String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE); if (dexMeta == null) { ShareTinkerLog.w(TAG, "patch recover, dex is not contained"); return true; } long begin = SystemClock.elapsedRealtime(); boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, useEmergencyMode, patchResult); long cost = SystemClock.elapsedRealtime() - begin; patchResult.dexCostTime = cost; ShareTinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost); return result; } protected static boolean waitAndCheckDexOptFile(File patchFile, Tinker manager) { if (optFiles.isEmpty()) { return true; } // should use patch list size int size = patchList.size() * 30; if (size > MAX_WAIT_COUNT) { size = MAX_WAIT_COUNT; } ShareTinkerLog.i(TAG, "raw dex count: %d, dex opt dex count: %d, final wait times: %d", patchList.size(), optFiles.size(), size); for (int i = 0; i < size; i++) { if (!checkAllDexOptFile(optFiles, i + 1)) { try { Thread.sleep(WAIT_ASYN_OAT_TIME); } catch (InterruptedException e) { ShareTinkerLog.e(TAG, "thread sleep InterruptedException e:" + e); } } } List failDexFiles = new ArrayList<>(); // check again, if still can't be found, just return for (File file : optFiles) { ShareTinkerLog.i(TAG, "check dex optimizer file exist: %s, size %d", file.getPath(), file.length()); if (!SharePatchFileUtil.isLegalFile(file) && !SharePatchFileUtil.shouldAcceptEvenIfIllegal(file)) { ShareTinkerLog.e(TAG, "final parallel dex optimizer file %s is not exist, return false", file.getName()); failDexFiles.add(file); } } if (!failDexFiles.isEmpty()) { manager.getPatchReporter().onPatchDexOptFail(patchFile, failDexFiles, new TinkerRuntimeException(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL)); return false; } if (Build.VERSION.SDK_INT >= 21) { Throwable lastThrowable = null; for (File file : optFiles) { if (SharePatchFileUtil.shouldAcceptEvenIfIllegal(file)) { continue; } ShareTinkerLog.i(TAG, "check dex optimizer file format: %s, size %d", file.getName(), file.length()); int returnType; try { returnType = ShareElfFile.getFileTypeByMagic(file); } catch (IOException e) { // read error just continue continue; } if (returnType == ShareElfFile.FILE_TYPE_ELF) { ShareElfFile elfFile = null; try { elfFile = new ShareElfFile(file); } catch (Throwable e) { ShareTinkerLog.e(TAG, "final parallel dex optimizer file %s is not elf format, return false", file.getName()); failDexFiles.add(file); lastThrowable = e; } finally { IOHelper.closeQuietly(elfFile); } } } if (!failDexFiles.isEmpty()) { Throwable returnThrowable = lastThrowable == null ? new TinkerRuntimeException(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL) : new TinkerRuntimeException(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL, lastThrowable); manager.getPatchReporter().onPatchDexOptFail(patchFile, failDexFiles, returnThrowable); return false; } } return true; } private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile, boolean useEmergencyMode, PatchResult patchResult) { String dir = patchVersionDirectory + "/" + DEX_PATH + "/"; if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) { ShareTinkerLog.w(TAG, "patch recover, extractDiffInternals fail"); return false; } File dexFiles = new File(dir); File[] files = dexFiles.listFiles(); List legalFiles = new ArrayList<>(); if (files != null) { for (File file : files) { final String fileName = file.getName(); // may have directory in android o if (file.isFile() && (fileName.endsWith(ShareConstants.DEX_SUFFIX) || fileName.endsWith(ShareConstants.JAR_SUFFIX) || fileName.endsWith(ShareConstants.PATCH_SUFFIX)) ) { legalFiles.add(file); } } } ShareTinkerLog.i(TAG, "legal files to do dexopt: " + legalFiles); final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/"; return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, useEmergencyMode, patchResult); } private static boolean checkClassNDexFiles(final String dexFilePath) { classNDexInfo.clear(); if (patchList.isEmpty() || !isVmArt) { return false; } ShareDexDiffPatchInfo testInfo = null; File testFile = null; for (ShareDexDiffPatchInfo info : patchList) { File dexFile = new File(dexFilePath + info.realName); String fileName = dexFile.getName(); if (ShareConstants.CLASS_N_PATTERN.matcher(fileName).matches()) { classNDexInfo.put(info, dexFile); } if (info.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) { testInfo = info; testFile = dexFile; } } if (testInfo != null) { classNDexInfo.put(ShareTinkerInternals.changeTestDexToClassN(testInfo, classNDexInfo.size() + 1), testFile); } File classNFile = new File(dexFilePath, ShareConstants.CLASS_N_APK_NAME); boolean result = true; if (classNFile.exists()) { for (ShareDexDiffPatchInfo info : classNDexInfo.keySet()) { if (!SharePatchFileUtil.verifyDexFileMd5(classNFile, info.rawName, info.destMd5InArt)) { ShareTinkerLog.e(TAG, "verify dex file md5 error, entry name; %s, file len: %d", info.rawName, classNFile.length()); result = false; break; } } if (!result) { SharePatchFileUtil.safeDeleteFile(classNFile); } } else { result = false; } if (result) { // delete classN dex if exist for (File dexFile : classNDexInfo.values()) { SharePatchFileUtil.safeDeleteFile(dexFile); } } return result; } private static ZipEntry makeStoredZipEntry(ZipEntry originalEntry, String realDexName) { final ZipEntry result = new ZipEntry(realDexName); result.setMethod(ZipEntry.STORED); result.setCompressedSize(originalEntry.getSize()); result.setSize(originalEntry.getSize()); result.setCrc(originalEntry.getCrc()); return result; } private static boolean mergeClassNDexFiles(final Context context, final File patchFile, final String dexFilePath) { // only merge for art vm if (patchList.isEmpty() || !isVmArt) { return true; } File classNFile = new File(dexFilePath, ShareConstants.CLASS_N_APK_NAME); // repack just more than one classN.dex if (classNDexInfo.isEmpty()) { ShareTinkerLog.w(TAG, "classNDexInfo size: %d, no need to merge classN dex files", classNDexInfo.size()); return true; } long start = System.currentTimeMillis(); boolean result = true; AlignedZipOutputStream out = null; try { if (classNFile.exists()) { classNFile.delete(); } out = new AlignedZipOutputStream(new BufferedOutputStream(new FileOutputStream(classNFile))); if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) { classNFile.setReadOnly(); } for (ShareDexDiffPatchInfo info : classNDexInfo.keySet()) { File dexFile = classNDexInfo.get(info); if (info.isJarMode) { ZipFile dexZipFile = null; InputStream inputStream = null; try { dexZipFile = new ZipFile(dexFile); ZipEntry rawDexZipEntry = dexZipFile.getEntry(ShareConstants.DEX_IN_JAR); ZipEntry newDexZipEntry = makeStoredZipEntry(rawDexZipEntry, info.rawName); inputStream = dexZipFile.getInputStream(rawDexZipEntry); try { out.putNextEntry(newDexZipEntry); IOHelper.copyStream(inputStream, out); } finally { out.closeEntry(); } } finally { IOHelper.closeQuietly(inputStream); IOHelper.closeQuietly(dexZipFile); } } else { ZipEntry newDexZipEntry = new ZipEntry(info.rawName); newDexZipEntry.setMethod(ZipEntry.STORED); newDexZipEntry.setCompressedSize(dexFile.length()); newDexZipEntry.setSize(dexFile.length()); newDexZipEntry.setCrc(DigestUtil.getCRC32(dexFile)); InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(dexFile)); try { out.putNextEntry(newDexZipEntry); IOHelper.copyStream(is, out); } finally { out.closeEntry(); } } finally { IOHelper.closeQuietly(is); } } } } catch (Throwable throwable) { ShareTinkerLog.printErrStackTrace(TAG, throwable, "merge classN file"); result = false; } finally { IOHelper.closeQuietly(out); } if (result) { for (ShareDexDiffPatchInfo info : classNDexInfo.keySet()) { if (!SharePatchFileUtil.verifyDexFileMd5(classNFile, info.rawName, info.destMd5InArt)) { result = false; ShareTinkerLog.e(TAG, "verify dex file md5 error, entry name; %s, file len: %d", info.rawName, classNFile.length()); break; } } } if (result) { for (File dexFile : classNDexInfo.values()) { SharePatchFileUtil.safeDeleteFile(dexFile); } } else { ShareTinkerLog.e(TAG, "merge classN dex error, try delete temp file"); SharePatchFileUtil.safeDeleteFile(classNFile); Tinker.with(context).getPatchReporter().onPatchTypeExtractFail(patchFile, classNFile, classNFile.getName(), TYPE_CLASS_N_DEX); } ShareTinkerLog.i(TAG, "merge classN dex file %s, result: %b, size: %d, use: %dms", classNFile.getPath(), result, classNFile.length(), (System.currentTimeMillis() - start)); return result; } private static boolean dexOptimizeDexFiles(Context context, List dexFiles, String optimizeDexDirectory, final File patchFile, boolean useEmergencyMode, final PatchResult patchResult) { final Tinker manager = Tinker.with(context); optFiles.clear(); if (dexFiles != null) { File optimizeDexDirectoryFile = new File(optimizeDexDirectory); if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) { ShareTinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail"); return false; } // add opt files for (File file : dexFiles) { String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile); optFiles.add(new File(outputPathName)); } ShareTinkerLog.i(TAG, "patch recover, try to optimize dex file count:%d, optimizeDexDirectory:%s", dexFiles.size(), optimizeDexDirectory); // only use parallel dex optimizer for art // for Android O version, it is very strange. If we use parallel dex optimizer, it won't work final List failOptDexFile = new Vector<>(); final Throwable[] throwable = new Throwable[1]; if (patchResult != null) { patchResult.dexoptTriggerTime = System.currentTimeMillis(); } final boolean useDLC = TinkerApplication.getInstance().isUseDelegateLastClassLoader(); final boolean[] anyOatNotGenerated = {false}; // try parallel dex optimizer TinkerDexOptimizer.optimizeAll( context, dexFiles, optimizeDexDirectoryFile, useDLC, useEmergencyMode, new TinkerDexOptimizer.ResultCallback() { long startTime; @Override public void onStart(File dexFile, File optimizedDir) { startTime = System.currentTimeMillis(); ShareTinkerLog.i(TAG, "start to parallel optimize dex %s, size: %d", dexFile.getPath(), dexFile.length()); } @Override public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) { ShareTinkerLog.i(TAG, "success to parallel optimize dex %s, opt file:%s, opt file size: %d, use time %d", dexFile.getPath(), optimizedFile.getPath(), optimizedFile.length(), (System.currentTimeMillis() - startTime)); if (!optimizedFile.exists()) { synchronized (anyOatNotGenerated) { anyOatNotGenerated[0] = true; } } } @Override public void onFailed(File dexFile, File optimizedDir, Throwable thr) { ShareTinkerLog.i(TAG, "fail to parallel optimize dex %s use time %d", dexFile.getPath(), (System.currentTimeMillis() - startTime)); failOptDexFile.add(dexFile); throwable[0] = thr; } } ); if (patchResult != null) { synchronized (anyOatNotGenerated) { patchResult.isOatGenerated = !anyOatNotGenerated[0]; } } if (!failOptDexFile.isEmpty()) { manager.getPatchReporter().onPatchDexOptFail(patchFile, failOptDexFile, throwable[0]); return false; } } return true; } /** * for ViVo or some other rom, they would make dex2oat asynchronous * so we need to check whether oat file is actually generated. * * @param files * @param count * @return */ private static boolean checkAllDexOptFile(ArrayList files, int count) { for (File file : files) { if (!SharePatchFileUtil.isLegalFile(file)) { if (SharePatchFileUtil.shouldAcceptEvenIfIllegal(file)) { continue; } ShareTinkerLog.e(TAG, "parallel dex optimizer file %s is not exist, just wait %d times", file.getName(), count); return false; } } return true; } private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) { //parse patchList.clear(); ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList); if (patchList.isEmpty()) { ShareTinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type)); return true; } File directory = new File(dir); if (!directory.exists()) { directory.mkdirs(); } //I think it is better to extract the raw files from apk Tinker manager = Tinker.with(context); ZipFile apk = null; ZipFile patch = null; try { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. ShareTinkerLog.w(TAG, "applicationInfo == null!!!!"); return false; } String apkPath = applicationInfo.sourceDir; apk = new ZipFile(apkPath); patch = new ZipFile(patchFile); if (checkClassNDexFiles(dir)) { ShareTinkerLog.w(TAG, "class n dex file %s is already exist, and md5 match, just continue", ShareConstants.CLASS_N_APK_NAME); return true; } for (ShareDexDiffPatchInfo info : patchList) { long start = System.currentTimeMillis(); final String infoPath = info.path; String patchRealPath; if (infoPath.equals("")) { patchRealPath = info.rawName; } else { patchRealPath = info.path + "/" + info.rawName; } String dexDiffMd5 = info.dexDiffMd5; String oldDexCrc = info.oldDexCrC; if (!isVmArt && info.destMd5InDvm.equals("0")) { ShareTinkerLog.w(TAG, "patch dex %s is only for art, just continue", patchRealPath); continue; } String extractedFileMd5 = isVmArt ? info.destMd5InArt : info.destMd5InDvm; if (!SharePatchFileUtil.checkIfMd5Valid(extractedFileMd5)) { ShareTinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } File extractedFile = new File(dir + info.realName); //check file whether already exist if (extractedFile.exists()) { if (SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { //it is ok, just continue ShareTinkerLog.w(TAG, "dex file %s is already exist, and md5 match, just continue", extractedFile.getPath()); continue; } else { ShareTinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath()); extractedFile.delete(); } } else { extractedFile.getParentFile().mkdirs(); } ZipEntry patchFileEntry = patch.getEntry(patchRealPath); ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath); if (oldDexCrc.equals("0")) { if (patchFileEntry == null) { ShareTinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } //it is a new file, but maybe we need to repack the dex file if (!extractDexFile(patch, patchFileEntry, extractedFile, info)) { ShareTinkerLog.w(TAG, "Failed to extract raw patch file " + extractedFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } } else if (dexDiffMd5.equals("0")) { // skip process old dex for real dalvik vm if (!isVmArt) { continue; } if (rawApkFileEntry == null) { ShareTinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } //check source crc instead of md5 for faster String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); if (!rawEntryCrc.equals(oldDexCrc)) { ShareTinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } // Small patched dex generating strategy was disabled, we copy full original dex directly now. //patchDexFile(apk, patch, rawApkFileEntry, null, info, smallPatchInfoFile, extractedFile); extractDexFile(apk, rawApkFileEntry, extractedFile, info); if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { ShareTinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); SharePatchFileUtil.safeDeleteFile(extractedFile); return false; } } else { if (patchFileEntry == null) { ShareTinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } if (!SharePatchFileUtil.checkIfMd5Valid(dexDiffMd5)) { ShareTinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, dexDiffMd5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } if (rawApkFileEntry == null) { ShareTinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } //check source crc instead of md5 for faster String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); if (!rawEntryCrc.equals(oldDexCrc)) { ShareTinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); return false; } patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile); if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) { ShareTinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type); SharePatchFileUtil.safeDeleteFile(extractedFile); return false; } ShareTinkerLog.w(TAG, "success recover dex file: %s, size: %d, use time: %d", extractedFile.getPath(), extractedFile.length(), (System.currentTimeMillis() - start)); } } if (!mergeClassNDexFiles(context, patchFile, dir)) { return false; } } catch (Throwable e) { throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); } finally { SharePatchFileUtil.closeZip(apk); SharePatchFileUtil.closeZip(patch); } return true; } /** * repack dex to jar * * @param zipFile * @param entryFile * @param extractTo * @param targetMd5 * @return boolean * @throws IOException */ private static boolean extractDexToJar(ZipFile zipFile, ZipEntry entryFile, File extractTo, String targetMd5) throws IOException { int numAttempts = 0; boolean isExtractionSuccessful = false; while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { numAttempts++; ZipOutputStream zos = null; BufferedInputStream bis = null; ShareTinkerLog.i(TAG, "try Extracting " + extractTo.getPath()); try { if (extractTo.exists()) { extractTo.delete(); } zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(extractTo))); if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) { extractTo.setReadOnly(); } bis = new BufferedInputStream(zipFile.getInputStream(entryFile)); byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; ZipEntry entry = new ZipEntry(ShareConstants.DEX_IN_JAR); zos.putNextEntry(entry); int length = bis.read(buffer); while (length != -1) { zos.write(buffer, 0, length); length = bis.read(buffer); } zos.closeEntry(); } finally { IOHelper.closeQuietly(bis); IOHelper.closeQuietly(zos); } isExtractionSuccessful = SharePatchFileUtil.verifyDexFileMd5(extractTo, targetMd5); ShareTinkerLog.i(TAG, "isExtractionSuccessful: %b", isExtractionSuccessful); if (!isExtractionSuccessful) { final boolean succ = extractTo.delete(); if (!succ || extractTo.exists()) { ShareTinkerLog.e(TAG, "Failed to delete corrupted dex " + extractTo.getPath()); } } } return isExtractionSuccessful; } // /** // * reject dalvik vm, but sdk version is larger than 21 // */ // private static void checkVmArtProperty() { // boolean art = ShareTinkerInternals.isVmArt(); // if (!art && Build.VERSION.SDK_INT >= 21) { // throw new TinkerRuntimeException(ShareConstants.CHECK_VM_PROPERTY_FAIL + ", it is dalvik vm, but sdk version " + Build.VERSION.SDK_INT + " is larger than 21!"); // } // } private static boolean extractDexFile(ZipFile zipFile, ZipEntry entryFile, File extractTo, ShareDexDiffPatchInfo dexInfo) throws IOException { final String fileMd5 = isVmArt ? dexInfo.destMd5InArt : dexInfo.destMd5InDvm; final String rawName = dexInfo.rawName; final boolean isJarMode = dexInfo.isJarMode; //it is raw dex and we use jar mode, so we need to zip it! if (SharePatchFileUtil.isRawDexFile(rawName) && isJarMode) { return extractDexToJar(zipFile, entryFile, extractTo, fileMd5); } return extract(zipFile, entryFile, extractTo, fileMd5, true); } /** * Generate patched dex file (May wrapped it by a jar if needed.) * * @param baseApk OldApk. * @param patchPkg Patch package, it is also a zip file. * @param oldDexEntry ZipEntry of old dex. * @param patchFileEntry ZipEntry of patch file. (also ends with .dex) This could be null. * @param patchInfo Parsed patch info from package-meta.txt * @param patchedDexFile Patched dex file, may be a jar. *

* Notice: patchFileEntry and smallPatchInfoFile cannot both be null. * @throws IOException */ private static void patchDexFile( ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry, ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException { InputStream oldDexStream = null; InputStream patchFileStream = null; try { oldDexStream = new BufferedInputStream(baseApk.getInputStream(oldDexEntry)); patchFileStream = (patchFileEntry != null ? new BufferedInputStream(patchPkg.getInputStream(patchFileEntry)) : null); if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) { patchedDexFile.setReadOnly(); } final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName); if (!isRawDexFile || patchInfo.isJarMode) { ZipOutputStream zos = null; try { if (patchedDexFile.exists()) { patchedDexFile.delete(); } zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile))); zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR)); // Old dex is not a raw dex file. if (!isRawDexFile) { ZipInputStream zis = null; try { zis = new ZipInputStream(oldDexStream); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break; } if (entry == null) { throw new TinkerRuntimeException("can't recognize zip dex format file:" + patchedDexFile.getAbsolutePath()); } new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos); } finally { IOHelper.closeQuietly(zis); } } else { new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(zos); } zos.closeEntry(); } finally { IOHelper.closeQuietly(zos); } } else { new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(patchedDexFile); } } finally { IOHelper.closeQuietly(oldDexStream); IOHelper.closeQuietly(patchFileStream); } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/ResDiffPatchInternal.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.SystemClock; import com.tencent.tinker.bsdiff.BSPatch; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.lib.filepatch.FilePatchFactory; import com.tencent.tinker.lib.service.PatchResult; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareResPatchInfo; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.ziputils.ziputil.TinkerZipEntry; import com.tencent.tinker.ziputils.ziputil.TinkerZipFile; import com.tencent.tinker.ziputils.ziputil.TinkerZipOutputStream; import com.tencent.tinker.ziputils.ziputil.TinkerZipUtil; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 2016/8/8. */ public class ResDiffPatchInternal extends BasePatchInternal { protected static final String TAG = "Tinker.ResDiffPatchInternal"; protected static boolean tryRecoverResourceFiles(Tinker manager, ShareSecurityCheck checker, Context context, String patchVersionDirectory, File patchFile, boolean useCustomPatcher, PatchResult patchResult) { if (!manager.isEnabledForResource()) { ShareTinkerLog.w(TAG, "patch recover, resource is not enabled"); return true; } String resourceMeta = checker.getMetaContentMap().get(RES_META_FILE); if (resourceMeta == null || resourceMeta.length() == 0) { ShareTinkerLog.w(TAG, "patch recover, resource is not contained"); return true; } long begin = SystemClock.elapsedRealtime(); boolean result = patchResourceExtractViaResourceDiff(context, patchVersionDirectory, resourceMeta, patchFile, useCustomPatcher); long cost = SystemClock.elapsedRealtime() - begin; patchResult.resCostTime = cost; ShareTinkerLog.i(TAG, "recover resource result:%b, cost:%d", result, cost); return result; } private static boolean patchResourceExtractViaResourceDiff(Context context, String patchVersionDirectory, String meta, File patchFile, boolean useCustomPatcher) { String dir = patchVersionDirectory + "/" + ShareConstants.RES_PATH + "/"; if (!extractResourceDiffInternals(context, dir, meta, patchFile, TYPE_RESOURCE, useCustomPatcher)) { ShareTinkerLog.w(TAG, "patch recover, extractDiffInternals fail"); return false; } return true; } private static boolean extractResourceDiffInternals(Context context, String dir, String meta, File patchFile, int type, boolean useCustomPatcher) { ShareResPatchInfo resPatchInfo = new ShareResPatchInfo(); ShareResPatchInfo.parseAllResPatchInfo(meta, resPatchInfo); ShareTinkerLog.i(TAG, "res dir: %s, meta: %s", dir, resPatchInfo.toString()); Tinker manager = Tinker.with(context); if (!SharePatchFileUtil.checkIfMd5Valid(resPatchInfo.resArscMd5)) { ShareTinkerLog.w(TAG, "resource meta file md5 mismatch, type:%s, md5: %s", ShareTinkerInternals.getTypeString(type), resPatchInfo.resArscMd5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } File directory = new File(dir); File tempResFileDirectory = new File(directory, "res_temp"); File resOutput = new File(directory, ShareConstants.RES_NAME); //check result file whether already exist if (resOutput.exists()) { if (SharePatchFileUtil.checkResourceArscMd5(resOutput, resPatchInfo.resArscMd5)) { //it is ok, just continue ShareTinkerLog.w(TAG, "resource file %s is already exist, and md5 match, just return true", resOutput.getPath()); return true; } else { ShareTinkerLog.w(TAG, "have a mismatch corrupted resource " + resOutput.getPath()); resOutput.delete(); } } else { resOutput.getParentFile().mkdirs(); } try { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { //Looks like running on a test Context, so just return without patching. ShareTinkerLog.w(TAG, "applicationInfo == null!!!!"); return false; } String apkPath = applicationInfo.sourceDir; if (!checkAndExtractResourceLargeFile(context, apkPath, directory, tempResFileDirectory, patchFile, resPatchInfo, type, useCustomPatcher)) { return false; } TinkerZipOutputStream out = null; TinkerZipFile oldApk = null; TinkerZipFile newApk = null; int totalEntryCount = 0; try { if (resOutput.exists()) { resOutput.delete(); } out = new TinkerZipOutputStream(new BufferedOutputStream(new FileOutputStream(resOutput))); if (ShareTinkerInternals.isNewerOrEqualThanVersion(33, true)) { resOutput.setReadOnly(); } oldApk = new TinkerZipFile(apkPath); newApk = new TinkerZipFile(patchFile); final Enumeration entries = oldApk.entries(); while (entries.hasMoreElements()) { TinkerZipEntry zipEntry = entries.nextElement(); if (zipEntry == null) { throw new TinkerRuntimeException("zipEntry is null when get from oldApk"); } String name = zipEntry.getName(); if (name.contains("../")) { continue; } if (ShareResPatchInfo.checkFileInPattern(resPatchInfo.patterns, name)) { //won't contain in add set. if (!resPatchInfo.deleteRes.contains(name) && !resPatchInfo.modRes.contains(name) && !resPatchInfo.largeModRes.contains(name) && !name.equals(ShareConstants.RES_MANIFEST)) { TinkerZipUtil.extractTinkerEntry(oldApk, zipEntry, out); totalEntryCount++; } } } //process manifest TinkerZipEntry manifestZipEntry = oldApk.getEntry(ShareConstants.RES_MANIFEST); if (manifestZipEntry == null) { ShareTinkerLog.w(TAG, "manifest patch entry is null. path:" + ShareConstants.RES_MANIFEST); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, ShareConstants.RES_MANIFEST, type); return false; } TinkerZipUtil.extractTinkerEntry(oldApk, manifestZipEntry, out); totalEntryCount++; for (String name : resPatchInfo.largeModRes) { TinkerZipEntry largeZipEntry = oldApk.getEntry(name); if (largeZipEntry == null) { ShareTinkerLog.w(TAG, "large patch entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type); return false; } ShareResPatchInfo.LargeModeInfo largeModeInfo = resPatchInfo.largeModMap.get(name); TinkerZipUtil.extractLargeModifyFile(largeZipEntry, largeModeInfo.file, largeModeInfo.crc, out); totalEntryCount++; } for (String name : resPatchInfo.addRes) { TinkerZipEntry addZipEntry = newApk.getEntry(name); if (addZipEntry == null) { ShareTinkerLog.w(TAG, "add patch entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type); return false; } if (resPatchInfo.storeRes.containsKey(name)) { File storeFile = resPatchInfo.storeRes.get(name); TinkerZipUtil.extractLargeModifyFile(addZipEntry, storeFile, addZipEntry.getCrc(), out); } else { TinkerZipUtil.extractTinkerEntry(newApk, addZipEntry, out); } totalEntryCount++; } for (String name : resPatchInfo.modRes) { TinkerZipEntry modZipEntry = newApk.getEntry(name); if (modZipEntry == null) { ShareTinkerLog.w(TAG, "mod patch entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, name, type); return false; } if (resPatchInfo.storeRes.containsKey(name)) { File storeFile = resPatchInfo.storeRes.get(name); TinkerZipUtil.extractLargeModifyFile(modZipEntry, storeFile, modZipEntry.getCrc(), out); } else { TinkerZipUtil.extractTinkerEntry(newApk, modZipEntry, out); } totalEntryCount++; } // set comment back out.setComment(oldApk.getComment()); } finally { IOHelper.closeQuietly(out); IOHelper.closeQuietly(oldApk); IOHelper.closeQuietly(newApk); //delete temp files SharePatchFileUtil.deleteDir(tempResFileDirectory); } boolean result = SharePatchFileUtil.checkResourceArscMd5(resOutput, resPatchInfo.resArscMd5); if (!result) { ShareTinkerLog.i(TAG, "check final new resource file fail path:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length()); SharePatchFileUtil.safeDeleteFile(resOutput); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, resOutput, ShareConstants.RES_NAME, type); return false; } ShareTinkerLog.i(TAG, "final new resource file:%s, entry count:%d, size:%d", resOutput.getAbsolutePath(), totalEntryCount, resOutput.length()); } catch (Throwable e) { throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); } return true; } private static boolean checkAndExtractResourceLargeFile(Context context, String apkPath, File directory, File tempFileDirtory, File patchFile, ShareResPatchInfo resPatchInfo, int type, boolean useCustomPatcher) { long start = System.currentTimeMillis(); Tinker manager = Tinker.with(context); ZipFile apkFile = null; ZipFile patchZipFile = null; try { //recover resources.arsc first apkFile = new ZipFile(apkPath); ZipEntry arscEntry = apkFile.getEntry(ShareConstants.RES_ARSC); File arscFile = new File(directory, ShareConstants.RES_ARSC); if (arscEntry == null) { ShareTinkerLog.w(TAG, "resources apk entry is null. path:" + ShareConstants.RES_ARSC); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, arscFile, ShareConstants.RES_ARSC, type); return false; } //use base resources.arsc crc to identify base.apk String baseArscCrc = String.valueOf(arscEntry.getCrc()); if (!baseArscCrc.equals(resPatchInfo.arscBaseCrc)) { ShareTinkerLog.e(TAG, "resources.arsc's crc is not equal, expect crc: %s, got crc: %s", resPatchInfo.arscBaseCrc, baseArscCrc); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, arscFile, ShareConstants.RES_ARSC, type); return false; } //resource arsc is not changed, just return true if (resPatchInfo.largeModRes.isEmpty() && resPatchInfo.storeRes.isEmpty()) { ShareTinkerLog.i(TAG, "no large modify or store resources, just return"); return true; } patchZipFile = new ZipFile(patchFile); for (String name : resPatchInfo.storeRes.keySet()) { long storeStart = System.currentTimeMillis(); File destCopy = new File(tempFileDirtory, name); SharePatchFileUtil.ensureFileDirectory(destCopy); ZipEntry patchEntry = patchZipFile.getEntry(name); if (patchEntry == null) { ShareTinkerLog.w(TAG, "store patch entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destCopy, name, type); return false; } extract(patchZipFile, patchEntry, destCopy, null, false); //fast check, only check size if (patchEntry.getSize() != destCopy.length()) { ShareTinkerLog.w(TAG, "resource meta file size mismatch, type:%s, name: %s, patch size: %d, file size; %d", ShareTinkerInternals.getTypeString(type), name, patchEntry.getSize(), destCopy.length()); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } resPatchInfo.storeRes.put(name, destCopy); ShareTinkerLog.w(TAG, "success recover store file:%s, file size:%d, use time:%d", destCopy.getPath(), destCopy.length(), (System.currentTimeMillis() - storeStart)); } for (String name : resPatchInfo.largeModRes) { long largeStart = System.currentTimeMillis(); ShareResPatchInfo.LargeModeInfo largeModeInfo = resPatchInfo.largeModMap.get(name); if (largeModeInfo == null) { ShareTinkerLog.w(TAG, "resource not found largeModeInfo, type:%s, name: %s", ShareTinkerInternals.getTypeString(type), name); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } largeModeInfo.file = new File(tempFileDirtory, name); SharePatchFileUtil.ensureFileDirectory(largeModeInfo.file); // we do not check the intermediate files' md5 to save time, use check whether it is 32 length if (!SharePatchFileUtil.checkIfMd5Valid(largeModeInfo.md5)) { ShareTinkerLog.w(TAG, "resource meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), name, largeModeInfo.md5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } ZipEntry patchEntry = patchZipFile.getEntry(name); if (patchEntry == null) { ShareTinkerLog.w(TAG, "large mod patch entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type); return false; } ZipEntry baseEntry = apkFile.getEntry(name); if (baseEntry == null) { ShareTinkerLog.w(TAG, "resources apk entry is null. path:" + name); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type); return false; } InputStream oldStream = null; InputStream newStream = null; try { oldStream = apkFile.getInputStream(baseEntry); newStream = patchZipFile.getInputStream(patchEntry); FilePatchFactory.getFilePatcher(context, useCustomPatcher).patchFast(oldStream, newStream, largeModeInfo.file); } finally { IOHelper.closeQuietly(oldStream); IOHelper.closeQuietly(newStream); } // go go go bsdiff get the if (!SharePatchFileUtil.verifyFileMd5(largeModeInfo.file, largeModeInfo.md5)) { ShareTinkerLog.w(TAG, "Failed to recover large modify file:%s", largeModeInfo.file.getPath()); SharePatchFileUtil.safeDeleteFile(largeModeInfo.file); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, largeModeInfo.file, name, type); return false; } ShareTinkerLog.w(TAG, "success recover large modify file:%s, file size:%d, use time:%d", largeModeInfo.file.getPath(), largeModeInfo.file.length(), (System.currentTimeMillis() - largeStart)); } ShareTinkerLog.w(TAG, "success recover all large modify and store resources use time:%d", (System.currentTimeMillis() - start)); } catch (Throwable e) { throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); } finally { SharePatchFileUtil.closeZip(apkFile); SharePatchFileUtil.closeZip(patchZipFile); } return true; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/SoDiffPatchInternal.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.SystemClock; import com.tencent.tinker.bsdiff.BSPatch; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.lib.filepatch.FilePatchFactory; import com.tencent.tinker.lib.service.PatchResult; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareBsDiffPatchInfo; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 16/3/21. */ public class SoDiffPatchInternal extends BasePatchInternal { private static final String TAG = "Tinker.BsDiffPatchInternal"; protected static boolean tryRecoverLibraryFiles(Tinker manager, ShareSecurityCheck checker, Context context, String patchVersionDirectory, File patchFile, boolean useCustomPatcher, PatchResult patchResult) { if (!manager.isEnabledForNativeLib()) { ShareTinkerLog.w(TAG, "patch recover, library is not enabled"); return true; } String libMeta = checker.getMetaContentMap().get(SO_META_FILE); if (libMeta == null) { ShareTinkerLog.w(TAG, "patch recover, library is not contained"); return true; } long begin = SystemClock.elapsedRealtime(); boolean result = patchLibraryExtractViaBsDiff(context, patchVersionDirectory, libMeta, patchFile, useCustomPatcher); long cost = SystemClock.elapsedRealtime() - begin; patchResult.soCostTime = cost; ShareTinkerLog.i(TAG, "recover lib result:%b, cost:%d", result, cost); return result; } private static boolean patchLibraryExtractViaBsDiff(Context context, String patchVersionDirectory, String meta, File patchFile, boolean useCustomPatcher) { String dir = patchVersionDirectory + "/" + SO_PATH + "/"; return extractBsDiffInternals(context, dir, meta, patchFile, TYPE_LIBRARY, useCustomPatcher); } private static boolean extractBsDiffInternals(Context context, String dir, String meta, File patchFile, int type, boolean useCustomPatcher) { //parse ArrayList patchList = new ArrayList<>(); ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, patchList); if (patchList.isEmpty()) { ShareTinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type)); return true; } File directory = new File(dir); if (!directory.exists()) { directory.mkdirs(); } //I think it is better to extract the raw files from apk Tinker manager = Tinker.with(context); ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. ShareTinkerLog.w(TAG, "applicationInfo == null!!!!"); return false; } ZipFile apk = null; ZipFile patch = null; try { String apkPath = applicationInfo.sourceDir; apk = new ZipFile(apkPath); patch = new ZipFile(patchFile); for (ShareBsDiffPatchInfo info : patchList) { long start = System.currentTimeMillis(); final String infoPath = info.path; String patchRealPath; if (infoPath.equals("")) { patchRealPath = info.name; } else { patchRealPath = info.path + "/" + info.name; } final String fileMd5 = info.md5; if (!SharePatchFileUtil.checkIfMd5Valid(fileMd5)) { ShareTinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, info.md5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } String middle; middle = info.path + "/" + info.name; File extractedFile = new File(dir + middle); //check file whether already exist if (extractedFile.exists()) { if (fileMd5.equals(SharePatchFileUtil.getMD5(extractedFile))) { //it is ok, just continue ShareTinkerLog.w(TAG, "bsdiff file %s is already exist, and md5 match, just continue", extractedFile.getPath()); continue; } else { ShareTinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath()); extractedFile.delete(); } } else { extractedFile.getParentFile().mkdirs(); } String patchFileMd5 = info.patchMd5; //it is a new file, just copy ZipEntry patchFileEntry = patch.getEntry(patchRealPath); if (patchFileEntry == null) { ShareTinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); return false; } if (patchFileMd5.equals("0")) { if (!extract(patch, patchFileEntry, extractedFile, fileMd5, false)) { ShareTinkerLog.w(TAG, "Failed to extract file " + extractedFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); return false; } } else { //we do not check the intermediate files' md5 to save time, use check whether it is 32 length if (!SharePatchFileUtil.checkIfMd5Valid(patchFileMd5)) { ShareTinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, patchFileMd5); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type)); return false; } ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath); if (rawApkFileEntry == null) { ShareTinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); return false; } String rawApkCrc = info.rawCrc; //check source crc instead of md5 for faster String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc()); if (!rawEntryCrc.equals(rawApkCrc)) { ShareTinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, rawApkCrc, rawEntryCrc); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); return false; } InputStream oldStream = null; InputStream newStream = null; try { oldStream = apk.getInputStream(rawApkFileEntry); newStream = patch.getInputStream(patchFileEntry); FilePatchFactory.getFilePatcher(context, useCustomPatcher).patchFast(oldStream, newStream, extractedFile); } finally { IOHelper.closeQuietly(oldStream); IOHelper.closeQuietly(newStream); } //go go go bsdiff get the if (!SharePatchFileUtil.verifyFileMd5(extractedFile, fileMd5)) { ShareTinkerLog.w(TAG, "Failed to recover diff file " + extractedFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type); SharePatchFileUtil.safeDeleteFile(extractedFile); return false; } ShareTinkerLog.w(TAG, "success recover bsdiff file: %s, use time: %d", extractedFile.getPath(), (System.currentTimeMillis() - start)); } } } catch (Throwable e) { throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e); } finally { SharePatchFileUtil.closeZip(apk); SharePatchFileUtil.closeZip(patch); } return true; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import android.os.Build; import com.tencent.tinker.lib.service.PatchResult; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.util.UpgradePatchRetry; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.IOException; import java.util.Map; /** * generate new patch, you can implement your own patch processor class * Created by zhangshaowen on 16/3/14. */ public class UpgradePatch extends AbstractPatch { private static final String TAG = "Tinker.UpgradePatch"; @Override public boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult) { Tinker manager = Tinker.with(context); final File patchFile = new File(tempPatchPath); if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return"); return false; } if (!SharePatchFileUtil.isLegalFile(patchFile)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return"); return false; } //check the signature, we should create a new checker ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context); int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck); if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail"); manager.getPatchReporter().onPatchPackageCheckFail(patchFile, returnCode); return false; } String patchMd5 = SharePatchFileUtil.getMD5(patchFile); if (patchMd5 == null) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return"); return false; } //use md5 as version patchResult.patchVersion = patchMd5; ShareTinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5); //check ok, we can real recover a new patch final File patchDirectoryFile = manager.getPatchDirectory(); final String patchDirectory = patchDirectoryFile.getAbsolutePath(); File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory); File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory); final Map pkgProps = signatureCheck.getPackagePropertiesIfPresent(); if (pkgProps == null) { ShareTinkerLog.e(TAG, "UpgradePatch packageProperties is null, do we process a valid patch apk ?"); return false; } final String isProtectedAppStr = pkgProps.get(ShareConstants.PKGMETA_KEY_IS_PROTECTED_APP); final boolean isProtectedApp = (isProtectedAppStr != null && !isProtectedAppStr.isEmpty() && !"0".equals(isProtectedAppStr)); final String useCustomPatchStr = pkgProps.get(ShareConstants.PKGMETA_KEY_USE_CUSTOM_FILE_PATCH); final boolean useCustomPatch = (useCustomPatchStr != null && !useCustomPatchStr.isEmpty() && !"0".equals(useCustomPatchStr)); SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); //it is a new patch, so we should not find a exist SharePatchInfo newInfo; //already have patch if (oldInfo != null) { if (oldInfo.oldVersion == null || oldInfo.newVersion == null || oldInfo.oatDir == null) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted"); manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion); return false; } if (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid", patchMd5); manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5); return false; } final boolean usingInterpret = oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH); if (!usingInterpret && !ShareTinkerInternals.isNullOrNil(oldInfo.newVersion) && oldInfo.newVersion.equals(patchMd5) && !oldInfo.newVersion.equals(oldInfo.versionToRemove)) { ShareTinkerLog.e(TAG, "patch already applied, md5: %s", patchMd5); // Reset patch apply retry count to let us be able to reapply without triggering // patch apply disable when we apply it successfully previously. UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5); return true; } // if it is interpret now, use changing flag to wait main process final String finalOatDir = usingInterpret ? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir; if (!patchMd5.equals(oldInfo.newVersion) && !oldInfo.newVersion.equals(oldInfo.oldVersion)) { // Mark previous new version to old one before delete current new version so that all processes can stay at the // same version after restart but before the latest new version finish applying. final String lastNewVersion = oldInfo.newVersion; oldInfo.newVersion = oldInfo.oldVersion; SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, oldInfo, patchInfoLockFile); } final String versionToRemove; if (patchMd5.equals(oldInfo.versionToRemove)) { // If we re-applied a patch that marks to be removed, clear the marker. versionToRemove = ""; } else { versionToRemove = oldInfo.versionToRemove; } newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, isProtectedApp, useCustomPatch, versionToRemove, Build.FINGERPRINT, finalOatDir, false); } else { newInfo = new SharePatchInfo("", patchMd5, isProtectedApp, useCustomPatch, "", Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH, false); } // it is a new patch, we first delete if there is any files // don't delete dir for faster retry // SharePatchFileUtil.deleteDir(patchVersionDirectory); final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); final String patchVersionDirectory = patchDirectory + "/" + patchName; ShareTinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory); //copy file File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5)); try { // check md5 first if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) { SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile); ShareTinkerLog.w(TAG, "UpgradePatch copy patch file, src file: %s size: %d, dest file: %s size:%d", patchFile.getAbsolutePath(), patchFile.length(), destPatchFile.getAbsolutePath(), destPatchFile.length()); } } catch (IOException e) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE); return false; } //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, useEmergencyMode, patchResult)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed"); return false; } if (!ArkHotDiffPatchInternal.tryRecoverArkHotLibrary(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) { return false; } if (!SoDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, useCustomPatch, patchResult)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed"); return false; } if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, useCustomPatch, patchResult)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed"); return false; } // check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed"); return false; } final File guardDirectory = SharePatchFileUtil.getGuardDirectory(patchDirectory); if (!guardDirectory.exists()) { guardDirectory.mkdirs(); } final File newVersionGuardLockFile = new File(guardDirectory, patchName); try { newVersionGuardLockFile.createNewFile(); } catch (IOException e) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, create guard lock file failed"); return false; } if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed"); manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion); return false; } // Reset patch apply retry count to let us be able to reapply without triggering // patch apply disable when we apply it successfully previously. UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5); ShareTinkerInternals.cleanPatchDirectoryWithGuard(patchDirectoryFile, patchName); ShareTinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok"); return true; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultLoadReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Context; import com.tencent.tinker.lib.service.TinkerPatchService; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.lib.util.UpgradePatchRetry; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by zhangshaowen on 16/3/10. * the default implement for LoadReporter * you can extent it for your own work * all is running in the process which loading the patch */ public class DefaultLoadReporter implements LoadReporter { private static final String TAG = "Tinker.DefaultLoadReporter"; protected final Context context; public DefaultLoadReporter(Context context) { this.context = context; } /** * we receive a patch, but it check fails by PatchListener * so we would not start a {@link TinkerPatchService} * * @param patchFile * @param errorCode errorCode define as following * {@code ShareConstants.ERROR_PATCH_OK} it is ok * {@code ShareConstants.ERROR_PATCH_DISABLE} patch is disable * {@code ShareConstants.ERROR_PATCH_NOTEXIST} the file of tempPatchPatch file is not exist * {@code ShareConstants.ERROR_PATCH_RUNNING} the recover service is running now, try later * {@code ShareConstants.ERROR_PATCH_INSERVICE} the recover service can't send patch request */ @Override public void onLoadPatchListenerReceiveFail(File patchFile, int errorCode) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadPatchListenerReceiveFail: patch receive fail: %s, code: %d", patchFile.getAbsolutePath(), errorCode); } /** * we can only handle patch version change in the main process, * we will need to kill all other process to ensure that all process's code is the same. * you can delete the old patch version file as {@link DefaultLoadReporter#onLoadPatchVersionChanged(String, String, File, String)} * or you can restart your other process here * * @param oldVersion * @param newVersion * @param patchDirectoryFile * @param currentPatchName */ @Override public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadPatchVersionChanged: patch version change from " + oldVersion + " to " + newVersion); if (oldVersion == null || newVersion == null) { return; } if (oldVersion.equals(newVersion)) { return; } //check main process if (!Tinker.with(context).isMainProcess()) { return; } // // Unnecessary now. Since other processes are killed in TinkerLoader. // ShareTinkerLog.i(TAG, "onLoadPatchVersionChanged, try kill all other process"); // // kill all other process to ensure that all process's code is the same. // ShareTinkerInternals.killAllOtherProcess(context); // reset retry count to 1, for interpret retry UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(newVersion); } /** * After system ota, we will try to load dex with interpret mode * * @param type type define as following * {@code ShareConstants.TYPE_INTERPRET_OK} it is ok, using interpret mode * {@code ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR} get instruction set from exist oat file fail * {@code ShareConstants.TYPE_INTERPRET_COMMAND_ERROR} use command line to generate interpret oat file fail * @param e */ @Override public void onLoadInterpret(int type, Throwable e) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadInterpret: type: %d, throwable: %s", type, e); switch (type) { case ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR: ShareTinkerLog.e(TAG, "patch loadReporter onLoadInterpret fail, can get instruction set from existed oat file"); break; case ShareConstants.TYPE_INTERPRET_COMMAND_ERROR: ShareTinkerLog.e(TAG, "patch loadReporter onLoadInterpret fail, command line to interpret return error"); break; case ShareConstants.TYPE_INTERPRET_OK: ShareTinkerLog.i(TAG, "patch loadReporter onLoadInterpret ok"); break; default: break; } retryPatch(); } /** * some files is not found, * we'd like to recover the old patch with {@link TinkerPatchService} in OldPatchProcessor mode * as {@link DefaultLoadReporter#onLoadFileNotFound(File, int, boolean)} * * @param file the missing file * @param fileType file type as following * {@code ShareConstants.TYPE_PATCH_FILE} patch file or directory not found * {@code ShareConstants.TYPE_PATCH_INFO} patch info file or directory not found * {@code ShareConstants.TYPE_DEX} patch dex file or directory not found * {@code ShareConstants.TYPE_LIBRARY} patch lib file or directory not found * {@code ShareConstants.TYPE_RESOURCE} patch lib file or directory not found * @param isDirectory whether is directory for the file type */ @Override public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadFileNotFound: patch file not found: %s, fileType: %d, isDirectory: %b", file.getAbsolutePath(), fileType, isDirectory); // only try to recover opt file // check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted if (fileType == ShareConstants.TYPE_DEX_OPT) { retryPatch(); } else { checkAndCleanPatch(); } } /** * default, we don't check file's md5 when we load them. but you can set {@code TinkerApplication.tinkerLoadVerifyFlag} * with tinker-android-anno, you can set {@code DefaultLifeCycle.loadVerifyFlag} * some files' md5 is mismatch with the meta.txt file * we won't load these files, clean patch for safety * * @param file the mismatch file * @param fileType file type, just now, only dex or library will go here * {@code ShareConstants.TYPE_DEX} patch dex file md5 mismatch * {@code ShareConstants.TYPE_LIBRARY} patch lib file md5 mismatch * {@code ShareConstants.TYPE_RESOURCE} patch resource file md5 mismatch */ @Override public void onLoadFileMd5Mismatch(File file, int fileType) { ShareTinkerLog.i(TAG, "patch load Reporter onLoadFileMd5Mismatch: patch file md5 mismatch file: %s, fileType: %d", file.getAbsolutePath(), fileType); //clean patch for safety checkAndCleanPatch(); } /** * when we load a new patch, we need to rewrite the patch.info file. * but patch info corrupted, we can't recover from it * we can clean patch as {@link DefaultLoadReporter#onLoadPatchInfoCorrupted(String, String, File)} * * @param oldVersion @nullable * @param newVersion @nullable * @param patchInfoFile */ @Override public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadPatchInfoCorrupted: patch info file damage: %s, from version: %s to version: %s", patchInfoFile.getAbsolutePath(), oldVersion, newVersion); checkAndCleanPatch(); } /** * the load patch process is end, we can see the cost times and the return code * return codes are define in {@link com.tencent.tinker.loader.shareutil.ShareConstants} * * @param patchDirectory the root patch directory {you_apk_data}/tinker * @param loadCode {@code ShareConstants.ERROR_LOAD_OK}, 0 means success * @param cost time in ms */ @Override public void onLoadResult(File patchDirectory, int loadCode, long cost) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadResult: patch load result, path:%s, code: %d, cost: %dms", patchDirectory.getAbsolutePath(), loadCode, cost); //you can just report the result here } /** * load patch occur unknown exception that we have wrap try catch for you! * you may need to report this exception and contact me * welcome to report a new issues for us! * you can disable patch as {@link DefaultLoadReporter#onLoadException(Throwable, int)} * * @param e * @param errorCode exception code * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN} unknown exception * {@code ShareConstants.ERROR_LOAD_EXCEPTION_DEX} exception when load dex * {@code ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE} exception when load resource * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT} exception unCaught */ @Override public void onLoadException(Throwable e, int errorCode) { //for unCaught or dex exception, disable tinker all the time with sp switch (errorCode) { case ShareConstants.ERROR_LOAD_EXCEPTION_DEX: if (e.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) { ShareTinkerLog.e(TAG, "patch loadReporter onLoadException: tinker dex check fail:" + e.getMessage()); } else { ShareTinkerLog.i(TAG, "patch loadReporter onLoadException: patch load dex exception: %s", e); } ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); ShareTinkerLog.i(TAG, "dex exception disable tinker forever with sp"); break; case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE: if (e.getMessage().contains(ShareConstants.CHECK_RES_INSTALL_FAIL)) { ShareTinkerLog.e(TAG, "patch loadReporter onLoadException: tinker res check fail:" + e.getMessage()); } else { ShareTinkerLog.i(TAG, "patch loadReporter onLoadException: patch load resource exception: %s", e); } ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); ShareTinkerLog.i(TAG, "res exception disable tinker forever with sp"); break; case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT: ShareTinkerLog.i(TAG, "patch loadReporter onLoadException: patch load unCatch exception: %s", e); ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); ShareTinkerLog.i(TAG, "unCaught exception disable tinker forever with sp"); String uncaughtString = SharePatchFileUtil.checkTinkerLastUncaughtCrash(context); if (!ShareTinkerInternals.isNullOrNil(uncaughtString)) { File laseCrashFile = SharePatchFileUtil.getPatchLastCrashFile(context); SharePatchFileUtil.safeDeleteFile(laseCrashFile); // found really crash reason ShareTinkerLog.e(TAG, "tinker uncaught real exception:" + uncaughtString); } break; case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN: ShareTinkerLog.i(TAG, "patch loadReporter onLoadException: patch load unknown exception: %s", e); //exception can be caught, it is no need to disable Tinker with sharedPreference break; default: break; } ShareTinkerLog.e(TAG, "tinker load exception, welcome to submit issue to us: https://github.com/Tencent/tinker/issues"); ShareTinkerLog.printErrStackTrace(TAG, e, "tinker load exception"); Tinker.with(context).setTinkerDisable(); checkAndCleanPatch(); } /** * check patch signature, TINKER_ID and meta files * * @param patchFile the loading path file * @param errorCode 0 is ok, you should define the errorCode yourself * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT} some patch file type is not supported for current tinkerFlag */ @Override public void onLoadPackageCheckFail(File patchFile, int errorCode) { ShareTinkerLog.i(TAG, "patch loadReporter onLoadPackageCheckFail: " + "load patch package check fail file path: %s, errorCode: %d", patchFile.getAbsolutePath(), errorCode); checkAndCleanPatch(); } public void checkAndCleanPatch() { Tinker tinker = Tinker.with(context); tinker.cleanPatch(); } public boolean retryPatch() { final Tinker tinker = Tinker.with(context); if (!tinker.isMainProcess()) { return false; } File patchVersionFile = tinker.getTinkerLoadResultIfPresent().patchVersionFile; if (patchVersionFile != null) { if (UpgradePatchRetry.getInstance(context).onPatchListenerCheck(SharePatchFileUtil.getMD5(patchVersionFile))) { ShareTinkerLog.i(TAG, "try to repair oat file on patch process"); TinkerInstaller.onReceiveUpgradePatch(context, patchVersionFile.getAbsolutePath()); return true; } // else { // ShareTinkerLog.i(TAG, "repair retry exceed must max time, just clean"); // checkAndCleanPatch(); // } } return false; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/DefaultPatchReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Context; import android.content.Intent; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.lib.util.UpgradePatchRetry; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.util.List; /** * Created by zhangshaowen on 16/3/14. * the default implement for PatchReporter * you can extent it for your own work * all is running in the :patch process */ public class DefaultPatchReporter implements PatchReporter { private static final String TAG = "Tinker.DefaultPatchReporter"; private static boolean shouldRetry = false; protected final Context context; public DefaultPatchReporter(Context context) { this.context = context; } /************************************ :patch process below ***************************************/ /** * use for report or some work at the beginning of TinkerPatchService * {@code TinkerPatchService.onHandleIntent} begin * * @param intent */ @Override public void onPatchServiceStart(Intent intent) { ShareTinkerLog.i(TAG, "patchReporter onPatchServiceStart: patch service start"); shouldRetry = false; UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent); } /** * check patch signature, TINKER_ID and meta files * * @param patchFile the loading path file * @param errorCode 0 is ok, you should define the errorCode yourself * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT} some patch file type is not supported for current tinkerFlag */ @Override public void onPatchPackageCheckFail(File patchFile, int errorCode) { ShareTinkerLog.i(TAG, "patchReporter onPatchPackageCheckFail: package check failed. path: %s, code: %d", patchFile.getAbsolutePath(), errorCode); //only meta corrupted, need to delete temp files. others is just in the check time! if (errorCode == ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED || errorCode == ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED || errorCode == ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED) { //delete temp files Tinker.with(context).cleanPatchByPatchApk(patchFile); } } /** * for upgrade patch, patchFileVersion can't equal oldVersion or newVersion in oldPatchInfo * for repair patch, oldPatchInfo can 't be null, and patchFileVersion must equal with oldVersion and newVersion * * @param patchFile the input patch file to recover * @param oldPatchInfo the current patch info * @param patchFileVersion it is the md5 of the input patchFile */ @Override public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) { ShareTinkerLog.i(TAG, "patchReporter onPatchVersionCheckFail: patch version exist. path: %s, version: %s", patchFile.getAbsolutePath(), patchFileVersion); //no need to delete temp files, because it is only in the check time! } /** * try to recover file fail * * @param patchFile the input patch file to recover * @param extractTo the target file * @param filename * @param fileType file type as following * {@code ShareConstants.TYPE_DEX} extract patch dex file fail * {@code ShareConstants.TYPE_DEX_FOR_ART} extract patch small art dex file fail * {@code ShareConstants.TYPE_LIBRARY} extract patch library fail * {@code ShareConstants.TYPE_PATCH_FILE} copy patch file fail * {@code ShareConstants.TYPE_RESOURCE} extract patch resource fail */ @Override public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) { ShareTinkerLog.i(TAG, "patchReporter onPatchTypeExtractFail: file extract fail type: %s, path: %s, extractTo: %s, filename: %s", ShareTinkerInternals.getTypeString(fileType), patchFile.getPath(), extractTo.getPath(), filename); //delete temp files Tinker.with(context).cleanPatchByPatchApk(patchFile); } /** * dex opt failed * * @param patchFile the input patch file to recover * @param dexFiles the dex files * @param t */ @Override public void onPatchDexOptFail(File patchFile, List dexFiles, Throwable t) { ShareTinkerLog.i(TAG, "patchReporter onPatchDexOptFail: dex opt fail path: %s, dex size: %d", patchFile.getAbsolutePath(), dexFiles.size()); ShareTinkerLog.printErrStackTrace(TAG, t, "onPatchDexOptFail:"); // some phone such as VIVO/OPPO like to change dex2oat to interpreted may go here // check oat file if it is elf format if (t.getMessage().contains(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL) || t.getMessage().contains(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL)) { shouldRetry = true; deleteOptFiles(dexFiles); } else { Tinker.with(context).cleanPatchByPatchApk(patchFile); } } /** * recover result, we will also send a result to {@link DefaultTinkerResultService} * * @param patchFile the input patch file to recover * @param success if it is success * @param cost cost time in ms */ @Override public void onPatchResult(File patchFile, boolean success, long cost) { ShareTinkerLog.i(TAG, "patchReporter onPatchResult: patch all result path: %s, success: %b, cost: %d", patchFile.getAbsolutePath(), success, cost); // if should retry don't delete the temp file if (!shouldRetry) { UpgradePatchRetry.getInstance(context).onPatchServiceResult(); } } /** * when we load a new patch, we need to rewrite the patch.info file. * but patch info corrupted, we can't recover from it * * @param patchFile the input patch file to recover * @param oldVersion old patch version * @param newVersion new patch version */ @Override public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) { ShareTinkerLog.i(TAG, "patchReporter onPatchInfoCorrupted: patch info is corrupted. old: %s, new: %s", oldVersion, newVersion); //patch.info is corrupted, just clean all patch Tinker.with(context).cleanPatch(); } /** * recover patch occur unknown exception that we have wrap try catch for you! * you may need to report this exception and contact me * welcome to report a new issues for us! * * @param patchFile the input file to patch * @param e */ @Override public void onPatchException(File patchFile, Throwable e) { ShareTinkerLog.i(TAG, "patchReporter onPatchException: patch exception path: %s, throwable: %s", patchFile.getAbsolutePath(), e.getMessage()); ShareTinkerLog.e(TAG, "tinker patch exception, welcome to submit issue to us: https://github.com/Tencent/tinker/issues"); // if (e.getMessage().contains(ShareConstants.CHECK_VM_PROPERTY_FAIL)) { // ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context); // ShareTinkerLog.i(TAG, "check vm property exception disable tinker forever with sp"); // } ShareTinkerLog.printErrStackTrace(TAG, e, "tinker patch exception"); //don't accept request any more! Tinker.with(context).setTinkerDisable(); ////delete temp files, I think we don't have to clean all patch Tinker.with(context).cleanPatchByPatchApk(patchFile); } private void deleteOptFiles(List dexFiles) { for (File file : dexFiles) { SharePatchFileUtil.safeDeleteFile(file); } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/LoadReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import com.tencent.tinker.lib.service.TinkerPatchService; import java.io.File; /** * Created by zhangshaowen on 16/3/10. */ public interface LoadReporter { /** * we receive a patch, but it check fails by PatchListener * so we would not start a {@link TinkerPatchService} * * @param patchFile * @param errorCode errorCode define as following * {@code ShareConstants.ERROR_PATCH_OK} it is ok * {@code ShareConstants.ERROR_PATCH_DISABLE} patch is disable * {@code ShareConstants.ERROR_PATCH_NOTEXIST} the file of tempPatchPatch file is not exist * {@code ShareConstants.ERROR_PATCH_RUNNING} the recover service is running now, try later * {@code ShareConstants.ERROR_PATCH_INSERVICE} the recover service can't send patch request */ void onLoadPatchListenerReceiveFail(File patchFile, int errorCode); /** * we can only handle patch version change in the main process, * we will need to kill all other process to ensure that all process's code is the same. * you can delete the old patch version file as {@link DefaultLoadReporter#onLoadPatchVersionChanged(String, String, File, String)} * or you can restart your other process here * * @param oldVersion * @param newVersion * @param patchDirectoryFile * @param currentPatchName */ void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName); /** * After system ota, we will try to load dex with interpret mode * @param type type define as following * {@code ShareConstants.TYPE_INTERPRET_OK} it is ok, using interpret mode * {@code ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR} get instruction set from exist oat file fail * {@code ShareConstants.TYPE_INTERPRET_COMMAND_ERROR} use command line to generate interpret oat file fail * @param e */ void onLoadInterpret(int type, Throwable e); /** * the load patch process is end, we can see the cost times and the return code * return codes are define in {@link com.tencent.tinker.loader.shareutil.ShareConstants} * * @param patchDirectory the root patch directory {you_apk_data}/tinker * @param loadCode {@code ShareConstants.ERROR_LOAD_OK}, 0 means success * @param cost time in MS */ void onLoadResult(File patchDirectory, int loadCode, long cost); /** * load patch occur unknown exception that we have wrap try catch for you! * you may need to report this exception and contact me * welcome to report a new issues for us! * you can disable patch as {@link DefaultLoadReporter#onLoadException(Throwable, int)} * * @param e * @param errorCode exception code * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN} unknown exception * {@code ShareConstants.ERROR_LOAD_EXCEPTION_DEX} exception when load dex * {@code ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE} exception when load resource * {@code ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT} exception unCaught */ void onLoadException(Throwable e, int errorCode); /** * some files is not found, * we'd like to recover the old patch with {@link TinkerPatchService} in OldPatchProcessor mode * as {@link DefaultLoadReporter#onLoadFileNotFound(File, int, boolean)} * * @param file the missing file * @param fileType file type as following * {@code ShareConstants.TYPE_PATCH_FILE} patch file or directory not found * {@code ShareConstants.TYPE_PATCH_INFO} patch info file or directory not found * {@code ShareConstants.TYPE_DEX} patch dex file or directory not found * {@code ShareConstants.TYPE_LIBRARY} patch lib file or directory not found * {@code ShareConstants.TYPE_RESOURCE} patch lib file or directory not found * * @param isDirectory whether is directory for the file type */ void onLoadFileNotFound(File file, int fileType, boolean isDirectory); /** * default, we don't check file's md5 when we load them. but you can set {@code TinkerApplication.tinkerLoadVerifyFlag} * with tinker-android-anno, you can set {@code DefaultLifeCycle.loadVerifyFlag} * some files' md5 is mismatch with the meta.txt file * we won't load these files, clean patch for safety * * @param file the mismatch file * @param fileType file type, just now, only dex or library will go here * {@code ShareConstants.TYPE_DEX} patch dex file md5 mismatch * {@code ShareConstants.TYPE_LIBRARY} patch lib file md5 mismatch * {@code ShareConstants.TYPE_RESOURCE} patch resource file md5 mismatch */ void onLoadFileMd5Mismatch(File file, int fileType); /** * when we load a new patch, we need to rewrite the patch.info file. * but patch info corrupted, we can't recover from it * we can clean patch as {@link DefaultLoadReporter#onLoadPatchInfoCorrupted(String, String, File)} * * @param oldVersion @nullable * @param newVersion @nullable * @param patchInfoFile */ void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile); /** * check patch signature, TINKER_ID and meta files * * @param patchFile the loading path file * @param errorCode 0 is ok, you should define the errorCode yourself * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk * {@code ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND} package meta: "assets/package_meta.txt" is not found * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT} some patch file type is not supported for current tinkerFlag */ void onLoadPackageCheckFail(File patchFile, int errorCode); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/reporter/PatchReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Intent; import com.tencent.tinker.lib.patch.UpgradePatch; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import java.io.File; import java.util.List; /** * Created by zhangshaowen on 16/3/14. * * means that it is a newly patch, we would default use {@link UpgradePatch} * to do the job */ public interface PatchReporter { /** * use for report or some work at the beginning of TinkerPatchService * {@code TinkerPatchService.onHandleIntent} begin * * @param intent */ void onPatchServiceStart(Intent intent); /** * check patch signature, TINKER_ID and meta files * * @param patchFile the loading path file * @param errorCode 0 is ok, you should define the errorCode yourself * {@code ShareConstants.ERROR_PACKAGE_CHECK_OK} it is ok * {@code ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL} patch file signature is not the same with the base apk * {@code ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED} dex meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED} lib meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in old apk manifest * {@code ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND} can't find TINKER_PATCH in patch meta file * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL} apk and patch's TINKER_PATCH value is not equal * {@code ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED} resource meta file's format check fail * {@code ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT} some patch file type is not supported for current tinkerFlag */ void onPatchPackageCheckFail(File patchFile, int errorCode); /** * for upgrade patch, patchFileVersion can't equal oldVersion or newVersion in oldPatchInfo * for repair patch, oldPatchInfo can 't be null, and patchFileVersion must equal with oldVersion and newVersion * * @param patchFile the input patch file to recover * @param oldPatchInfo the current patch info * @param patchFileVersion it is the md5 of the input patchFile */ void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion); /** * try to recover file fail * * @param patchFile the input patch file to recover * @param extractTo the target file * @param filename * @param fileType file type as following * {@code ShareConstants.TYPE_DEX} extract patch dex file fail * {@code ShareConstants.TYPE_DEX_FOR_ART} extract patch small art dex file fail * {@code ShareConstants.TYPE_LIBRARY} extract patch library fail * {@code ShareConstants.TYPE_PATCH_FILE} copy patch file fail * {@code ShareConstants.TYPE_RESOURCE} extract patch resource fail */ void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType); /** * dex opt failed * * @param patchFile the input patch file to recover * @param dexFiles the dex file * @param t throwable */ void onPatchDexOptFail(File patchFile, List dexFiles, Throwable t); /** * recover result, we will also send a result to {@link DefaultTinkerResultService} * * @param patchFile the input patch file to recover * @param success if it is success * @param cost cost time in ms */ void onPatchResult(File patchFile, boolean success, long cost); /** * recover patch occur unknown exception that we have wrap try catch for you! * you may need to report this exception and contact me * welcome to report a new issues for us! * * @param patchFile the input file to patch * @param e */ void onPatchException(File patchFile, Throwable e); /** * when we load a new patch, we need to rewrite the patch.info file. * but patch info corrupted, we can't recover from it * * @param patchFile the input patch file to recover * @param oldVersion old patch version * @param newVersion new patch version */ void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/AbstractResultService.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import android.app.IntentService; import android.content.Context; import android.content.Intent; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; /** * Created by zhangshaowen on 16/3/14. */ public abstract class AbstractResultService extends IntentService { private static final String TAG = "Tinker.AbstractResultService"; private static final String RESULT_EXTRA = "result_extra"; public AbstractResultService() { super("TinkerResultService"); } public static void runResultService(Context context, PatchResult result, String resultServiceClass) { if (resultServiceClass == null) { throw new TinkerRuntimeException("resultServiceClass is null."); } try { Intent intent = new Intent(); intent.setClassName(context, resultServiceClass); intent.putExtra(RESULT_EXTRA, result); context.startService(intent); } catch (Throwable throwable) { ShareTinkerLog.e(TAG, "run result service fail, exception:" + throwable); } } @Override protected void onHandleIntent(Intent intent) { if (intent == null) { ShareTinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring."); return; } PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA); onPatchResult(result); } public abstract void onPatchResult(PatchResult result); } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/DefaultTinkerResultService.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerLoadResult; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.lib.util.TinkerServiceInternals; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import java.io.File; /** * Created by zhangshaowen on 16/3/19. */ public class DefaultTinkerResultService extends AbstractResultService { private static final String TAG = "Tinker.DefaultTinkerResultService"; /** * we may want to use the new patch just now!! * * @param result */ @Override public void onPatchResult(PatchResult result) { if (result == null) { ShareTinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!"); return; } ShareTinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString()); //first, we want to kill the recover process TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); // if success and newPatch, it is nice to delete the raw file, and restart at once // only main process can load an upgrade patch! if (result.isSuccess) { deleteRawPatchFile(new File(result.rawPatchFilePath)); if (checkIfNeedKill(result)) { android.os.Process.killProcess(android.os.Process.myPid()); } else { ShareTinkerLog.i(TAG, "I have already install the newly patch version!"); } } } /** * don't delete tinker version file * @param rawFile */ public void deleteRawPatchFile(File rawFile) { if (!SharePatchFileUtil.isLegalFile(rawFile)) { return; } ShareTinkerLog.w(TAG, "deleteRawPatchFile rawFile path: %s", rawFile.getPath()); String fileName = rawFile.getName(); if (!fileName.startsWith(ShareConstants.PATCH_BASE_NAME) || !fileName.endsWith(ShareConstants.PATCH_SUFFIX)) { SharePatchFileUtil.safeDeleteFile(rawFile); return; } File parentFile = rawFile.getParentFile(); if (!parentFile.getName().startsWith(ShareConstants.PATCH_BASE_NAME)) { SharePatchFileUtil.safeDeleteFile(rawFile); } else { File grandFile = parentFile.getParentFile(); if (!grandFile.getName().equals(ShareConstants.PATCH_DIRECTORY_NAME) && !grandFile.getName().equals(ShareConstants.PATCH_DIRECTORY_NAME_SPEC)) { SharePatchFileUtil.safeDeleteFile(rawFile); } } } public boolean checkIfNeedKill(PatchResult result) { Tinker tinker = Tinker.with(getApplicationContext()); if (tinker.isTinkerLoaded()) { TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent(); if (tinkerLoadResult != null) { String currentVersion = tinkerLoadResult.currentVersion; if (result.patchVersion != null && result.patchVersion.equals(currentVersion)) { return false; } } } return true; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import java.io.Serializable; /** * Created by zhangshaowen on 16/3/19. */ public class PatchResult implements Serializable { public static final int PATCH_TYPE_UNKNOWN = -1; public static final int PATCH_TYPE_BSDIFF = 0; public static final int PATCH_TYPE_CUSTOM = 1; public boolean isSuccess; public String rawPatchFilePath; public boolean useEmergencyMode; public long totalCostTime; public long dexCostTime; public long soCostTime; public long resCostTime; public int type = PATCH_TYPE_UNKNOWN; public long dexoptTriggerTime; public boolean isOatGenerated; public Throwable e; //@Nullable public String patchVersion; @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("\nPatchResult: \n"); sb.append("isSuccess:" + isSuccess + "\n"); sb.append("rawPatchFilePath:" + rawPatchFilePath + "\n"); sb.append("useEmergencyMode:" + useEmergencyMode + "\n"); sb.append("costTime:" + totalCostTime + "\n"); sb.append("dexoptTriggerTime:" + dexoptTriggerTime + "\n"); sb.append("isOatGenerated:" + isOatGenerated + "\n"); if (patchVersion != null) { sb.append("patchVersion:" + patchVersion + "\n"); } if (e != null) { sb.append("Throwable:" + e.getMessage() + "\n"); } return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchForeService.java ================================================ package com.tencent.tinker.lib.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import com.tencent.tinker.lib.IForeService; public class TinkerPatchForeService extends Service { public TinkerPatchForeService() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); // It's unwelcome to restart owner process of this service automatically for users. // So return START_NOT_STICKY here to prevent this behavior. return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return new IForeService.Stub() { @Override public void startme() throws RemoteException { //占位使用,不做具体操作 } }; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import static com.tencent.tinker.lib.util.TinkerServiceInternals.getTinkerPatchServiceName; import android.app.ActivityManager; import android.app.IntentService; import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; import com.tencent.tinker.lib.patch.AbstractPatch; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * Created by zhangshaowen on 16/3/14. */ public class TinkerPatchService extends IntentService { private static final String TAG = "Tinker.TinkerPatchService"; private static final String PATCH_PATH_EXTRA = "patch_path_extra"; private static final String PATCH_USE_EMERGENCY_MODE = "patch_use_emergency_mode"; private static final String RESULT_CLASS_EXTRA = "patch_result_class"; private static AbstractPatch upgradePatchProcessor = null; private static int notificationId = ShareConstants.TINKER_PATCH_SERVICE_NOTIFICATION; private static Class resultServiceClass = null; public TinkerPatchService() { super("TinkerPatchService"); setIntentRedelivery(true); } public static void runPatchService(final Context context, final String path) { runPatchService(context, path, false); } public static void runPatchService(final Context context, final String path, boolean useEmergencyMode) { ShareTinkerLog.i(TAG, "run patch service..."); Intent intent = new Intent(context, TinkerPatchService.class); intent.putExtra(PATCH_PATH_EXTRA, path); intent.putExtra(PATCH_USE_EMERGENCY_MODE, useEmergencyMode); intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName()); try { context.startService(intent); } catch (Throwable thr) { ShareTinkerLog.e(TAG, "run patch service fail, exception:" + thr); } } public static void setPatchProcessor(AbstractPatch upgradePatch, Class serviceClass) { upgradePatchProcessor = upgradePatch; resultServiceClass = serviceClass; //try to load try { Class.forName(serviceClass.getName()); } catch (ClassNotFoundException e) { ShareTinkerLog.printErrStackTrace(TAG, e, "patch processor class not found."); } } public static String getPatchPathExtra(Intent intent) { if (intent == null) { throw new TinkerRuntimeException("getPatchPathExtra, but intent is null"); } return ShareIntentUtil.getStringExtra(intent, PATCH_PATH_EXTRA); } public static boolean getPatchUseEmergencyMode(Intent intent) { if (intent == null) { throw new TinkerRuntimeException("getPatchUseEmergencyMode, but intent is null"); } return ShareIntentUtil.getBooleanExtra(intent, PATCH_USE_EMERGENCY_MODE, false); } public static String getPatchResultExtra(Intent intent) { if (intent == null) { throw new TinkerRuntimeException("getPatchResultExtra, but intent is null"); } return ShareIntentUtil.getStringExtra(intent, RESULT_CLASS_EXTRA); } @Override protected void onHandleIntent(Intent intent) { increasingPriority(); doApplyPatch(this, intent); } /** * set the tinker notification id you want * @param id */ public static void setTinkerNotificationId(int id) { notificationId = id; } private static final String RUNNING_MARKER_FILE_RELPATH_PREFIX = "patch_service_status/running_"; /** * Check if TinkerPatchService is running. * @param context */ public static boolean isRunning(Context context) { try { final String serviceName = getTinkerPatchServiceName(context); if (serviceName == null) { return false; } final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return false; } final List runningProcInfos = am.getRunningAppProcesses(); if (runningProcInfos == null || runningProcInfos.size() == 0) { return false; } int targetPid = 0; for (ActivityManager.RunningAppProcessInfo procInfo : runningProcInfos) { if (procInfo.processName.equals(serviceName)) { targetPid = procInfo.pid; break; } } if (targetPid == 0) { return false; } final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context); final File runningMarkerFile = new File(tinkerBaseDir, RUNNING_MARKER_FILE_RELPATH_PREFIX + targetPid); return runningMarkerFile.exists(); } catch (Throwable ignored) { return false; } } static void markRunning(Context context) { final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context); final File runningMarkerFile = new File(tinkerBaseDir, RUNNING_MARKER_FILE_RELPATH_PREFIX + android.os.Process.myPid()); if (runningMarkerFile.exists()) { return; } final File runningMarkerDir = runningMarkerFile.getParentFile(); if (runningMarkerDir.exists()) { final File[] markerFiles = runningMarkerDir.listFiles(); if (markerFiles != null) { for (File markerFile : markerFiles) { markerFile.delete(); } } } else { runningMarkerDir.mkdirs(); } try { if (!runningMarkerFile.createNewFile()) { throw new IllegalStateException(); } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to create running marker file."); } } static void unmarkRunning(Context context) { final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context); final File runningMarkerFile = new File(tinkerBaseDir, RUNNING_MARKER_FILE_RELPATH_PREFIX + android.os.Process.myPid()); if (runningMarkerFile.exists()) { runningMarkerFile.delete(); } } private static AtomicBoolean sIsPatchApplying = new AtomicBoolean(false); private static void doApplyPatch(Context context, Intent intent) { // Since we may retry with IntentService, we should prevent // racing here again. if (!sIsPatchApplying.compareAndSet(false, true)) { ShareTinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner."); return; } try { markRunning(context); Tinker tinker = Tinker.with(context); tinker.getPatchReporter().onPatchServiceStart(intent); if (intent == null) { ShareTinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring."); return; } String path = getPatchPathExtra(intent); if (path == null) { ShareTinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring."); return; } File patchFile = new File(path); final boolean useEmergencyMode = getPatchUseEmergencyMode(intent); long begin = SystemClock.elapsedRealtime(); boolean result; long cost; Throwable e = null; PatchResult patchResult = new PatchResult(); try { if (upgradePatchProcessor == null) { throw new TinkerRuntimeException("upgradePatchProcessor is null."); } result = upgradePatchProcessor.tryPatch(context, path, useEmergencyMode, patchResult); } catch (Throwable throwable) { e = throwable; result = false; tinker.getPatchReporter().onPatchException(patchFile, e); } cost = SystemClock.elapsedRealtime() - begin; tinker.getPatchReporter() .onPatchResult(patchFile, result, cost); patchResult.isSuccess = result; patchResult.rawPatchFilePath = path; patchResult.useEmergencyMode = useEmergencyMode; patchResult.totalCostTime = cost; patchResult.type = tinker.getCustomPatcher() == null ? PatchResult.PATCH_TYPE_BSDIFF : PatchResult.PATCH_TYPE_CUSTOM; patchResult.e = e; unmarkRunning(context); sIsPatchApplying.set(false); AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent)); } finally { unmarkRunning(context); } } private void increasingPriority() { if (Build.VERSION.SDK_INT >= 26) { ShareTinkerLog.i(TAG, "for system version >= Android O, we just ignore increasingPriority " + "job to avoid crash or toasts."); return; } if ("ZUK".equals(Build.MANUFACTURER)) { ShareTinkerLog.i(TAG, "for ZUK device, we just ignore increasingPriority " + "job to avoid crash."); return; } ShareTinkerLog.i(TAG, "try to increase patch process priority"); try { Notification notification = new Notification(); if (Build.VERSION.SDK_INT < 18) { startForeground(notificationId, notification); } else { startForeground(notificationId, notification); // start InnerService startService(new Intent(this, InnerService.class)); } } catch (Throwable e) { ShareTinkerLog.i(TAG, "try to increase patch process priority error:" + e); } } /** * I don't want to do this, believe me */ public static class InnerService extends Service { @Override public void onCreate() { super.onCreate(); try { startForeground(notificationId, new Notification()); } catch (Throwable e) { ShareTinkerLog.e(TAG, "InnerService set service for push exception:%s.", e); } stopSelf(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); // It's unwelcome to restart owner process of this service automatically for users. // So return START_NOT_STICKY here to prevent this behavior. return START_NOT_STICKY; } @Override public void onDestroy() { stopForeground(true); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import android.content.Intent; import com.tencent.tinker.lib.filepatch.AbstractFilePatch; import com.tencent.tinker.lib.filepatch.FilePatchFactory; import com.tencent.tinker.lib.listener.DefaultPatchListener; import com.tencent.tinker.lib.listener.PatchListener; import com.tencent.tinker.lib.patch.AbstractPatch; import com.tencent.tinker.lib.patch.UpgradePatch; import com.tencent.tinker.lib.reporter.DefaultLoadReporter; import com.tencent.tinker.lib.reporter.DefaultPatchReporter; import com.tencent.tinker.lib.reporter.LoadReporter; import com.tencent.tinker.lib.reporter.PatchReporter; import com.tencent.tinker.lib.service.AbstractResultService; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.lib.service.TinkerPatchService; import com.tencent.tinker.lib.util.TinkerServiceInternals; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by zhangshaowen on 16/3/10. */ public class Tinker { private static final String TAG = "Tinker.Tinker"; private static Tinker sInstance; private static boolean sInstalled = false; final Context context; /** * data dir, such as /data/data/tinker.sample.android/tinker */ final File patchDirectory; final PatchListener listener; final LoadReporter loadReporter; final PatchReporter patchReporter; final File patchInfoFile; final File patchInfoLockFile; final AbstractFilePatch customPatcher; final boolean isMainProcess; final boolean isPatchProcess; /** * same with {@code TinkerApplication.tinkerLoadVerifyFlag} */ final boolean tinkerLoadVerifyFlag; /** * same with {@code TinkerApplication.tinkerFlags} */ int tinkerFlags; TinkerLoadResult tinkerLoadResult; /** * whether load patch success */ private boolean loaded = false; private Tinker(Context context, int tinkerFlags, LoadReporter loadReporter, PatchReporter patchReporter, PatchListener listener, File patchDirectory, File patchInfoFile, File patchInfoLockFile, AbstractFilePatch customPatcher, boolean isInMainProc, boolean isPatchProcess, boolean tinkerLoadVerifyFlag) { this.context = context; this.listener = listener; this.loadReporter = loadReporter; this.patchReporter = patchReporter; this.tinkerFlags = tinkerFlags; this.patchDirectory = patchDirectory; this.patchInfoFile = patchInfoFile; this.patchInfoLockFile = patchInfoLockFile; this.customPatcher = customPatcher; this.isMainProcess = isInMainProc; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.isPatchProcess = isPatchProcess; } /** * init with default config tinker * for safer, you must use @{link TinkerInstaller.install} first! * * @param context we will use the application context * @return the Tinker object */ public static Tinker with(Context context) { if (!sInstalled) { throw new TinkerRuntimeException("you must install tinker before get tinker sInstance"); } synchronized (Tinker.class) { if (sInstance == null) { sInstance = new Builder(context).build(); } } return sInstance; } /** * create custom tinker by {@link Tinker.Builder} * please do it when very first your app start. * * @param tinker */ public static void create(Tinker tinker) { if (sInstance != null) { throw new TinkerRuntimeException("Tinker instance is already set."); } sInstance = tinker; } public static boolean isTinkerInstalled() { return sInstalled; } /** * you must install tinker first!! * * @param intentResult * @param serviceClass * @param upgradePatch */ public void install(Intent intentResult, Class serviceClass, AbstractPatch upgradePatch) { sInstalled = true; TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass); ShareTinkerLog.i(TAG, "try to install tinker, isEnable: %b, version: %s", isTinkerEnabled(), ShareConstants.TINKER_VERSION); if (!isTinkerEnabled()) { ShareTinkerLog.e(TAG, "tinker is disabled"); return; } if (intentResult == null) { throw new TinkerRuntimeException("intentResult must not be null."); } tinkerLoadResult = new TinkerLoadResult(); tinkerLoadResult.parseTinkerResult(getContext(), intentResult); //after load code set loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime); if (!loaded) { ShareTinkerLog.w(TAG, "tinker load fail!"); } } /** * set tinkerPatchServiceNotificationId * * @param id */ public void setPatchServiceNotificationId(int id) { TinkerPatchService.setTinkerNotificationId(id); } /** * Nullable, should check the loaded flag first */ public TinkerLoadResult getTinkerLoadResultIfPresent() { return tinkerLoadResult; } public void install(Intent intentResult) { install(intentResult, DefaultTinkerResultService.class, new UpgradePatch()); } public Context getContext() { return context; } public boolean isMainProcess() { return isMainProcess; } public boolean isPatchProcess() { return isPatchProcess; } public void setTinkerDisable() { tinkerFlags = ShareConstants.TINKER_DISABLE; } public LoadReporter getLoadReporter() { return loadReporter; } public PatchReporter getPatchReporter() { return patchReporter; } public boolean isTinkerEnabled() { return ShareTinkerInternals.isTinkerEnabled(tinkerFlags); } public boolean isTinkerLoaded() { return loaded; } public void setTinkerLoaded(boolean isLoaded) { loaded = isLoaded; } public boolean isTinkerLoadVerify() { return tinkerLoadVerifyFlag; } public boolean isEnabledForDex() { return ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags); } public boolean isEnabledForNativeLib() { return ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlags); } public boolean isEnabledForResource() { return ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlags); } public File getPatchDirectory() { return patchDirectory; } public File getPatchInfoFile() { return patchInfoFile; } public File getPatchInfoLockFile() { return patchInfoLockFile; } public AbstractFilePatch getCustomPatcher() { return customPatcher; } public PatchListener getPatchListener() { return listener; } public int getTinkerFlags() { return tinkerFlags; } /** * clean all patch files */ public void cleanPatch() { ShareTinkerInternals.cleanPatch(getContext()); } /** * rollback patch should restart all process */ public void rollbackPatch() { if (!isTinkerLoaded()) { ShareTinkerLog.w(TAG, "rollbackPatch: tinker is not loaded, just return"); return; } // kill all other process ShareTinkerInternals.killAllOtherProcess(context); // clean patch cleanPatch(); // kill itself android.os.Process.killProcess(android.os.Process.myPid()); } /** * clean the patch version files, such as tinker/patch-641e634c * * @deprecated It is unsafe, never clean patch manually. * * @param versionName */ @Deprecated public void cleanPatchByVersion(String versionName) { if (patchDirectory == null || versionName == null) { return; } String path = patchDirectory.getAbsolutePath() + "/" + versionName; SharePatchFileUtil.deleteDir(path); } /** * get the rom size of tinker, use kb * * @return */ public long getTinkerRomSpace() { if (patchDirectory == null) { return 0; } return SharePatchFileUtil.getFileOrDirectorySize(patchDirectory) / 1024; } /** * try delete the temp version files * * @deprecated It is unsafe, never clean patch manually. * * @param patchApk */ @Deprecated public void cleanPatchByPatchApk(File patchApk) { if (patchDirectory == null || patchApk == null || !patchApk.exists()) { return; } String versionName = SharePatchFileUtil.getPatchVersionDirectory(SharePatchFileUtil.getMD5(patchApk)); cleanPatchByVersion(versionName); } public static class Builder { private final Context context; private final boolean mainProcess; private final boolean patchProcess; private int status = -1; private LoadReporter loadReporter; private PatchReporter patchReporter; private PatchListener listener; private AbstractFilePatch patcher; private File patchDirectory; private File patchInfoFile; private File patchInfoLockFile; private Boolean tinkerLoadVerifyFlag; /** * Start building a new {@link Tinker} instance. */ public Builder(Context context) { if (context == null) { throw new TinkerRuntimeException("Context must not be null."); } this.context = context; this.mainProcess = TinkerServiceInternals.isInMainProcess(context); this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context); this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context); if (this.patchDirectory == null) { ShareTinkerLog.e(TAG, "patchDirectory is null!"); return; } this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath()); this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath()); ShareTinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory); } public Builder tinkerFlags(int tinkerFlags) { if (this.status != -1) { throw new TinkerRuntimeException("tinkerFlag is already set."); } this.status = tinkerFlags; return this; } public Builder tinkerLoadVerifyFlag(Boolean verifyMd5WhenLoad) { if (verifyMd5WhenLoad == null) { throw new TinkerRuntimeException("tinkerLoadVerifyFlag must not be null."); } if (this.tinkerLoadVerifyFlag != null) { throw new TinkerRuntimeException("tinkerLoadVerifyFlag is already set."); } this.tinkerLoadVerifyFlag = verifyMd5WhenLoad; return this; } public Builder loadReport(LoadReporter loadReporter) { if (loadReporter == null) { throw new TinkerRuntimeException("loadReporter must not be null."); } if (this.loadReporter != null) { throw new TinkerRuntimeException("loadReporter is already set."); } this.loadReporter = loadReporter; return this; } public Builder patchReporter(PatchReporter patchReporter) { if (patchReporter == null) { throw new TinkerRuntimeException("patchReporter must not be null."); } if (this.patchReporter != null) { throw new TinkerRuntimeException("patchReporter is already set."); } this.patchReporter = patchReporter; return this; } public Builder listener(PatchListener listener) { if (listener == null) { throw new TinkerRuntimeException("listener must not be null."); } if (this.listener != null) { throw new TinkerRuntimeException("listener is already set."); } this.listener = listener; return this; } public Builder customPatcher(AbstractFilePatch patcher) { this.patcher = patcher; return this; } public Tinker build() { if (status == -1) { status = ShareConstants.TINKER_ENABLE_ALL; } if (loadReporter == null) { loadReporter = new DefaultLoadReporter(context); } if (patchReporter == null) { patchReporter = new DefaultPatchReporter(context); } if (listener == null) { listener = new DefaultPatchListener(context); } if (tinkerLoadVerifyFlag == null) { tinkerLoadVerifyFlag = false; } return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory, patchInfoFile, patchInfoLockFile, patcher, mainProcess, patchProcess, tinkerLoadVerifyFlag); } } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerApplicationHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Intent; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.util.HashMap; import java.util.Map; /** * sometimes, you may want to install tinker later, or never install tinker in some process. * you can use {@code TinkerApplicationHelper} API to get the tinker status! * Created by zhangshaowen on 16/6/28. */ public class TinkerApplicationHelper { private static final String TAG = "Tinker.TinkerApplicationHelper"; /** * they can use without Tinker is installed! * same as {@code Tinker.isTinkerEnabled} * * @return */ public static boolean isTinkerEnableAll(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } int tinkerFlags = applicationLike.getTinkerFlags(); return ShareTinkerInternals.isTinkerEnabledAll(tinkerFlags); } /** * same as {@code Tinker.isEnabledForDex} * * @param applicationLike * @return */ public static boolean isTinkerEnableForDex(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } int tinkerFlags = applicationLike.getTinkerFlags(); return ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags); } /** * same as {@code Tinker.isEnabledForNativeLib} * * @param applicationLike * @return */ public static boolean isTinkerEnableForNativeLib(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } int tinkerFlags = applicationLike.getTinkerFlags(); return ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlags); } /** * same as {@code Tinker.isTinkerEnabledForResource} * * @param applicationLike * @return */ public static boolean isTinkerEnableForResource(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } int tinkerFlags = applicationLike.getTinkerFlags(); return ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlags); } /** * same as {@code Tinker.getPatchDirectory} * * @param applicationLike * @return */ public static File getTinkerPatchDirectory(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } return SharePatchFileUtil.getPatchDirectory(applicationLike.getApplication()); } /** * whether tinker is success loaded * same as {@code Tinker.isTinkerLoaded} * * @param applicationLike * @return */ public static boolean isTinkerLoadSuccess(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); if (tinkerResultIntent == null) { return false; } int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); return (loadCode == ShareConstants.ERROR_LOAD_OK); } /** * you can use this api to get load dexes before tinker is installed * same as {@code Tinker.getTinkerLoadResultIfPresent.dexes} * * @return */ public static HashMap getLoadDexesAndMd5(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); if (tinkerResultIntent == null) { return null; } int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); if (loadCode == ShareConstants.ERROR_LOAD_OK) { return ShareIntentUtil.getIntentPatchDexPaths(tinkerResultIntent); } return null; } /** * you can use this api to get load libs before tinker is installed * same as {@code Tinker.getTinkerLoadResultIfPresent.libs} * * @return */ public static HashMap getLoadLibraryAndMd5(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); if (tinkerResultIntent == null) { return null; } int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); if (loadCode == ShareConstants.ERROR_LOAD_OK) { return ShareIntentUtil.getIntentPatchLibsPaths(tinkerResultIntent); } return null; } /** * you can use this api to get tinker package configs before tinker is installed * same as {@code Tinker.getTinkerLoadResultIfPresent.packageConfig} * * @return */ public static HashMap getPackageConfigs(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); if (tinkerResultIntent == null) { return null; } int loadCode = ShareIntentUtil.getIntentReturnCode(tinkerResultIntent); if (loadCode == ShareConstants.ERROR_LOAD_OK) { return ShareIntentUtil.getIntentPackageConfig(tinkerResultIntent); } return null; } /** * you can use this api to get tinker current version before tinker is installed * * @return */ public static String getCurrentVersion(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } Intent tinkerResultIntent = applicationLike.getTinkerResultIntent(); if (tinkerResultIntent == null) { return null; } final String oldVersion = ShareIntentUtil.getStringExtra(tinkerResultIntent, ShareIntentUtil.INTENT_PATCH_OLD_VERSION); final String newVersion = ShareIntentUtil.getStringExtra(tinkerResultIntent, ShareIntentUtil.INTENT_PATCH_NEW_VERSION); final boolean isMainProcess = ShareTinkerInternals.isInMainProcess(applicationLike.getApplication()); if (oldVersion != null && newVersion != null) { if (isMainProcess) { return newVersion; } else { return oldVersion; } } return null; } /** * clean all patch files without install tinker * same as {@code Tinker.cleanPatch} * * @param applicationLike */ public static void cleanPatch(ApplicationLike applicationLike) { if (applicationLike == null || applicationLike.getApplication() == null) { throw new TinkerRuntimeException("tinkerApplication is null"); } ShareTinkerInternals.cleanPatch(applicationLike.getApplication()); } /** * only support auto load lib/armeabi-v7a library from patch. * in some process, you may not want to install tinker * and you can load patch dex and library without install tinker! * } */ public static void loadArmV7aLibrary(ApplicationLike applicationLike, String libName) { if (libName == null || libName.isEmpty() || applicationLike == null) { throw new TinkerRuntimeException("libName or context is null!"); } if (TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { if (TinkerApplicationHelper.loadLibraryFromTinker(applicationLike, "lib/armeabi-v7a", libName)) { return; } } System.loadLibrary(libName); } /** * only support auto load lib/armeabi library from patch. * in some process, you may not want to install tinker * and you can load patch dex and library without install tinker! */ public static void loadArmLibrary(ApplicationLike applicationLike, String libName) { if (libName == null || libName.isEmpty() || applicationLike == null) { throw new TinkerRuntimeException("libName or context is null!"); } if (TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { if (TinkerApplicationHelper.loadLibraryFromTinker(applicationLike, "lib/armeabi", libName)) { return; } } System.loadLibrary(libName); } /** * you can use these api to load tinker library without tinker is installed! * same as {@code TinkerInstaller#loadLibraryFromTinker} * * @param applicationLike * @param relativePath * @param libname * @return * @throws UnsatisfiedLinkError */ public static boolean loadLibraryFromTinker(ApplicationLike applicationLike, String relativePath, String libname) throws UnsatisfiedLinkError { libname = libname.startsWith("lib") ? libname : "lib" + libname; libname = libname.endsWith(".so") ? libname : libname + ".so"; String relativeLibPath = relativePath + "/" + libname; //TODO we should add cpu abi, and the real path later if (!TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { return false; } if (!TinkerApplicationHelper.isTinkerEnableForNativeLib(applicationLike)) { return false; } final HashMap loadLibraries = TinkerApplicationHelper.getLoadLibraryAndMd5(applicationLike); if (loadLibraries == null) { return false; } final String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike); if (ShareTinkerInternals.isNullOrNil(currentVersion)) { return false; } final File patchDirectory = SharePatchFileUtil.getPatchDirectory(applicationLike.getApplication()); if (patchDirectory == null) { return false; } final File patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + SharePatchFileUtil.getPatchVersionDirectory(currentVersion)); final String libPrePath = patchVersionDirectory.getAbsolutePath() + "/" + ShareConstants.SO_PATH; for (Map.Entry libEntry : loadLibraries.entrySet()) { final String name = libEntry.getKey(); if (!name.equals(relativeLibPath)) { continue; } final String patchLibraryPath = libPrePath + "/" + name; final File library = new File(patchLibraryPath); if (!library.exists()) { continue; } //whether we check md5 when load final boolean verifyMd5 = applicationLike.getTinkerLoadVerifyFlag(); if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadLibraries.get(name))) { //do not report, because tinker is not install ShareTinkerLog.i(TAG, "loadLibraryFromTinker md5mismatch fail:" + patchLibraryPath); } else { System.load(patchLibraryPath); ShareTinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath); return true; } } return false; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerInstaller.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.lib.filepatch.AbstractFilePatch; import com.tencent.tinker.lib.filepatch.FilePatchFactory; import com.tencent.tinker.lib.listener.PatchListener; import com.tencent.tinker.lib.patch.AbstractPatch; import com.tencent.tinker.lib.reporter.LoadReporter; import com.tencent.tinker.lib.reporter.PatchReporter; import com.tencent.tinker.lib.service.AbstractResultService; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/19. */ public class TinkerInstaller { private static final String TAG = "Tinker.TinkerInstaller"; /** * install tinker with default config, you must install tinker before you use their api * or you can just use {@link TinkerApplicationHelper}'s api * * @param applicationLike */ public static Tinker install(ApplicationLike applicationLike) { Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent()); return tinker; } /** * install tinker with custom config, you must install tinker before you use their api * or you can just use {@link TinkerApplicationHelper}'s api * * @param applicationLike * @param loadReporter * @param patchReporter * @param listener * @param resultServiceClass * @param upgradePatchProcessor */ public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter, PatchListener listener, Class resultServiceClass, AbstractPatch upgradePatchProcessor) { Tinker tinker = new Tinker.Builder(applicationLike.getApplication()) .tinkerFlags(applicationLike.getTinkerFlags()) .loadReport(loadReporter) .listener(listener) .patchReporter(patchReporter) .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor); return tinker; } public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter, PatchListener listener, Class resultServiceClass, AbstractPatch upgradePatchProcessor, AbstractFilePatch filePatch) { Tinker tinker = new Tinker.Builder(applicationLike.getApplication()) .tinkerFlags(applicationLike.getTinkerFlags()) .loadReport(loadReporter) .listener(listener) .patchReporter(patchReporter) .customPatcher(filePatch) .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor); return tinker; } /** * clean all patch files! * * @param context */ public static void cleanPatch(Context context) { Tinker.with(context).cleanPatch(); } /** * new patch file to install, try install them with :patch process * * @param context * @param patchLocation */ public static void onReceiveUpgradePatch(Context context, String patchLocation) { Tinker.with(context).getPatchListener().onPatchReceived(patchLocation); } /** * set logIml for ShareTinkerLog * * @param imp */ public static void setLogIml(ShareTinkerLog.TinkerLogImp imp) { ShareTinkerLog.setTinkerLogImp(imp); } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/TinkerLoadResult.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import android.content.Intent; import android.os.Build; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.util.HashMap; /** * Created by zhangshaowen on 16/3/25. */ public class TinkerLoadResult { private static final String TAG = "Tinker.TinkerLoadResult"; //@Nullable public SharePatchInfo patchInfo; //@Nullable public String currentVersion; //@Nullable public String oatDir; public boolean versionChanged; public boolean useInterpretMode; public boolean systemOTA; //@Nullable public File patchVersionDirectory; //@Nullable public File patchVersionFile; //@Nullable public File dexDirectory; //@Nullable public File libraryDirectory; //@Nullable public File resourceDirectory; //@Nullable public File resourceFile; //@Nullable public HashMap dexes; //@Nullable public HashMap libs; //@Nullable public HashMap packageConfig; public int loadCode; public long costTime; public boolean parseTinkerResult(Context context, Intent intentResult) { Tinker tinker = Tinker.with(context); loadCode = ShareIntentUtil.getIntentReturnCode(intentResult); costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult); systemOTA = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, false); oatDir = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OAT_DIR); useInterpretMode = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH.equals(oatDir); final boolean isMainProcess = tinker.isMainProcess(); ShareTinkerLog.i(TAG, "parseTinkerResult loadCode:%d, process name:%s, main process:%b, systemOTA:%b, fingerPrint:%s, oatDir:%s, useInterpretMode:%b", loadCode, ShareTinkerInternals.getProcessName(context), isMainProcess, systemOTA, Build.FINGERPRINT, oatDir, useInterpretMode); //@Nullable final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION); //@Nullable final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION); final File patchDirectory = tinker.getPatchDirectory(); final File patchInfoFile = tinker.getPatchInfoFile(); if (oldVersion != null && newVersion != null) { if (isMainProcess) { currentVersion = newVersion; } else { currentVersion = oldVersion; } ShareTinkerLog.i(TAG, "parseTinkerResult oldVersion:%s, newVersion:%s, current:%s", oldVersion, newVersion, currentVersion); //current version may be nil String patchName = SharePatchFileUtil.getPatchVersionDirectory(currentVersion); if (!ShareTinkerInternals.isNullOrNil(patchName)) { patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + patchName); patchVersionFile = new File(patchVersionDirectory.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(currentVersion)); dexDirectory = new File(patchVersionDirectory, ShareConstants.DEX_PATH); libraryDirectory = new File(patchVersionDirectory, ShareConstants.SO_PATH); resourceDirectory = new File(patchVersionDirectory, ShareConstants.RES_PATH); resourceFile = new File(resourceDirectory, ShareConstants.RES_NAME); } final boolean isProtectedApp = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_IS_PROTECTED_APP, false); final boolean useCustomPatch = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_USE_CUSTOM_PATCH, false); patchInfo = new SharePatchInfo(oldVersion, newVersion, isProtectedApp, useCustomPatch,"", Build.FINGERPRINT, oatDir, false); versionChanged = !(oldVersion.equals(newVersion)); } //found uncaught exception, just return Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult); if (exception != null) { ShareTinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode); int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; switch (loadCode) { case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX; break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE; break; case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT; break; default: break; } tinker.getLoadReporter().onLoadException(exception, errorCode); return false; } switch (loadCode) { case ShareConstants.ERROR_LOAD_GET_INTENT_FAIL: ShareTinkerLog.e(TAG, "can't get the right intent return code"); throw new TinkerRuntimeException("can't get the right intent return code"); case ShareConstants.ERROR_LOAD_DISABLE: ShareTinkerLog.w(TAG, "tinker is disable, just return"); break; // case ShareConstants.ERROR_LOAD_PATCH_NOT_SUPPORTED: // ShareTinkerLog.w(TAG, "tinker is not supported, just return"); // break; case ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST: case ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST: ShareTinkerLog.w(TAG, "can't find patch file, is ok, just return"); break; case ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED: ShareTinkerLog.e(TAG, "path info corrupted"); tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); break; case ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK: ShareTinkerLog.e(TAG, "path info blank, wait main process to restart"); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST: ShareTinkerLog.e(TAG, "patch version directory not found, current version:%s", currentVersion); tinker.getLoadReporter().onLoadFileNotFound(patchVersionDirectory, ShareConstants.TYPE_PATCH_FILE, true); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST: ShareTinkerLog.e(TAG, "patch version file not found, current version:%s", currentVersion); if (patchVersionFile == null) { throw new TinkerRuntimeException("error load patch version file not exist, but file is null"); } tinker.getLoadReporter().onLoadFileNotFound(patchVersionFile, ShareConstants.TYPE_PATCH_FILE, false); break; case ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL: ShareTinkerLog.i(TAG, "patch package check fail"); if (patchVersionFile == null) { throw new TinkerRuntimeException("error patch package check fail , but file is null"); } int errorCode = intentResult.getIntExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); tinker.getLoadReporter().onLoadPackageCheckFail(patchVersionFile, errorCode); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST: if (dexDirectory != null) { ShareTinkerLog.e(TAG, "patch dex file directory not found:%s", dexDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(dexDirectory, ShareConstants.TYPE_DEX, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch dex file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch dex file directory not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST: String dexPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); if (dexPath != null) { //we only pass one missing file ShareTinkerLog.e(TAG, "patch dex file not found:%s", dexPath); tinker.getLoadReporter().onLoadFileNotFound(new File(dexPath), ShareConstants.TYPE_DEX, false); } else { ShareTinkerLog.e(TAG, "patch dex file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch dex file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_DEX, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST: String dexOptPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); if (dexOptPath != null) { //we only pass one missing file ShareTinkerLog.e(TAG, "patch dex opt file not found:%s", dexOptPath); tinker.getLoadReporter().onLoadFileNotFound(new File(dexOptPath), ShareConstants.TYPE_DEX_OPT, false); } else { ShareTinkerLog.e(TAG, "patch dex opt file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch dex opt file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_DEX, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch lib file directory not found:%s", libraryDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(libraryDirectory, ShareConstants.TYPE_LIBRARY, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch lib file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch lib file directory not found, warning why the path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_LIBRARY, true); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST: String libPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH); if (libPath != null) { //we only pass one missing file and then we break ShareTinkerLog.e(TAG, "patch lib file not found:%s", libPath); tinker.getLoadReporter().onLoadFileNotFound(new File(libPath), ShareConstants.TYPE_LIBRARY, false); } else { ShareTinkerLog.e(TAG, "patch lib file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch lib file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_LIBRARY, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL: ShareTinkerLog.e(TAG, "patch dex load fail, classloader is null"); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH: String mismatchPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH); if (mismatchPath == null) { ShareTinkerLog.e(TAG, "patch dex file md5 is mismatch, but path is null!!!!"); throw new TinkerRuntimeException("patch dex file md5 is mismatch, but path is null!!!!"); } else { ShareTinkerLog.e(TAG, "patch dex file md5 is mismatch: %s", mismatchPath); tinker.getLoadReporter().onLoadFileMd5Mismatch(new File(mismatchPath), ShareConstants.TYPE_DEX); } break; case ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL: ShareTinkerLog.i(TAG, "rewrite patch info file corrupted"); tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch resource file directory not found:%s", resourceDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(resourceDirectory, ShareConstants.TYPE_RESOURCE, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch resource file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch resource file directory not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch resource file not found:%s", resourceFile.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(resourceFile, ShareConstants.TYPE_RESOURCE, false); } else { //should be not here ShareTinkerLog.e(TAG, "patch resource file not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch resource file not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH: if (resourceFile == null) { ShareTinkerLog.e(TAG, "resource file md5 mismatch, but patch resource file not found!"); throw new TinkerRuntimeException("resource file md5 mismatch, but patch resource file not found!"); } ShareTinkerLog.e(TAG, "patch resource file md5 is mismatch: %s", resourceFile.getAbsolutePath()); tinker.getLoadReporter().onLoadFileMd5Mismatch(resourceFile, ShareConstants.TYPE_RESOURCE); break; case ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION: tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult)); break; case ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION: tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_COMMAND_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult)); break; case ShareConstants.ERROR_LOAD_OK: ShareTinkerLog.i(TAG, "oh yeah, tinker load all success"); tinker.setTinkerLoaded(true); // get load dex dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult); libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult); packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult); if (useInterpretMode) { tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_OK, null); } if (isMainProcess && versionChanged) { //change the old version to new tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName()); } return true; default: break; } return false; } /** * get package configs * * @param name * @return */ public String getPackageConfigByName(String name) { if (packageConfig != null) { return packageConfig.get(name); } return null; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerLog.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/17. * * DEPRECATED. Use {@link ShareTinkerLog} instead. */ @Deprecated public class TinkerLog { private static final String TAG = "Tinker.TinkerLog"; public static void setTinkerLogImp(TinkerLogImp imp) { ShareTinkerLog.setTinkerLogImp(imp); } public static ShareTinkerLog.TinkerLogImp getImpl() { return ShareTinkerLog.getImpl(); } public static void v(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void e(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void w(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void i(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void d(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj) { ShareTinkerLog.printErrStackTrace(tag, tr, format, obj); } public static void printPendingLogs() { ShareTinkerLog.printPendingLogs(); } public interface TinkerLogImp extends ShareTinkerLog.TinkerLogImp {} } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/TinkerServiceInternals.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import android.app.ActivityManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import com.tencent.tinker.lib.service.TinkerPatchService; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.util.List; /** * Created by zhangshaowen on 16/3/10. */ public class TinkerServiceInternals extends ShareTinkerInternals { private static final String TAG = "Tinker.ServiceInternals"; /** * or you may just hardcode them in your app */ private static String patchServiceProcessName = null; public static void killTinkerPatchServiceProcess(Context context) { String serverProcessName = getTinkerPatchServiceName(context); if (serverProcessName == null) { return; } final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); // ActivityManager getRunningAppProcesses() List appProcessList = am .getRunningAppProcesses(); if (appProcessList == null) { return; } for (ActivityManager.RunningAppProcessInfo appProcess : appProcessList) { String processName = appProcess.processName; if (processName.equals(serverProcessName)) { android.os.Process.killProcess(appProcess.pid); } } } public static boolean isTinkerPatchServiceRunning(Context context) { return TinkerPatchService.isRunning(context); } public static String getTinkerPatchServiceName(final Context context) { if (patchServiceProcessName != null) { return patchServiceProcessName; } //may be null, and you may like to hardcode instead String serviceName = TinkerServiceInternals.getServiceProcessName(context, TinkerPatchService.class); if (serviceName == null) { return null; } patchServiceProcessName = serviceName; return patchServiceProcessName; } /** * add service cache * * @param context * @return boolean */ public static boolean isInTinkerPatchServiceProcess(Context context) { String process = getProcessName(context); String service = TinkerServiceInternals.getTinkerPatchServiceName(context); if (service == null || service.length() == 0) { return false; } return process.equals(service); } private static String getServiceProcessName(Context context, Class serviceClass) { PackageManager packageManager = context.getPackageManager(); ComponentName component = new ComponentName(context, serviceClass); ServiceInfo serviceInfo; try { serviceInfo = packageManager.getServiceInfo(component, 0); } catch (Throwable ignored) { // Service is disabled. return null; } return serviceInfo.processName; } } ================================================ FILE: tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/util/UpgradePatchRetry.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import android.content.Context; import android.content.Intent; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.lib.service.TinkerPatchService; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; /** * optional * tinker :patch process may killed by some reason, we can retry it to increase upgrade success rate * if patch file is at sdcard, copy it to dataDir first. because some software may delete it. * * Created by zhangshaowen on 16/7/3. */ public class UpgradePatchRetry { private static final String TAG = "Tinker.UpgradePatchRetry"; private static final String RETRY_INFO_NAME = "patch.retry"; private static final String TEMP_PATCH_NAME = "temp.apk"; private static final String RETRY_FILE_MD5_PROPERTY = "md5"; private static final String RETRY_COUNT_PROPERTY = "times"; private static final int RETRY_MAX_COUNT = 20; private static UpgradePatchRetry sInstance; private boolean isRetryEnable = true; private File retryInfoFile = null; private File tempPatchFile = null; private Context context = null; private int maxRetryCount = RETRY_MAX_COUNT; /** * you must set after tinker has installed * * @param context */ public UpgradePatchRetry(Context context) { this.context = context; retryInfoFile = new File(SharePatchFileUtil.getPatchTempDirectory(context), RETRY_INFO_NAME); tempPatchFile = new File(SharePatchFileUtil.getPatchTempDirectory(context), TEMP_PATCH_NAME); } public static UpgradePatchRetry getInstance(Context context) { if (sInstance == null) { sInstance = new UpgradePatchRetry(context); } return sInstance; } public void setRetryEnable(boolean enable) { isRetryEnable = enable; } public void setMaxRetryCount(int count) { if (count <= 0) { ShareTinkerLog.e(TAG, "max count must large than 0"); return; } maxRetryCount = count; } public boolean onPatchRetryLoad() { if (!isRetryEnable) { ShareTinkerLog.w(TAG, "onPatchRetryLoad retry disabled, just return"); return false; } Tinker tinker = Tinker.with(context); //only retry on main process if (!tinker.isMainProcess()) { ShareTinkerLog.w(TAG, "onPatchRetryLoad retry is not main process, just return"); return false; } if (!retryInfoFile.exists()) { ShareTinkerLog.w(TAG, "onPatchRetryLoad retry info not exist, just return"); return false; } if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) { ShareTinkerLog.w(TAG, "onPatchRetryLoad tinker service is running, just return"); return false; } //must use temp file String path = tempPatchFile.getAbsolutePath(); if (path == null || !new File(path).exists()) { ShareTinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is not exist, just return", path); return false; } ShareTinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is exist, retry to patch", path); TinkerInstaller.onReceiveUpgradePatch(context, path); return true; } public void onPatchServiceStart(Intent intent) { if (!isRetryEnable) { ShareTinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return"); return; } if (intent == null) { ShareTinkerLog.e(TAG, "onPatchServiceStart intent is null, just return"); return; } String path = TinkerPatchService.getPatchPathExtra(intent); if (path == null) { ShareTinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return"); return; } RetryInfo retryInfo; File patchFile = new File(path); String patchMd5 = SharePatchFileUtil.getMD5(patchFile); if (patchMd5 == null) { ShareTinkerLog.w(TAG, "onPatchServiceStart patch md5 is null, just return"); return; } if (retryInfoFile.exists()) { retryInfo = RetryInfo.readRetryProperty(retryInfoFile); if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) { copyToTempFile(patchFile); retryInfo.md5 = patchMd5; retryInfo.times = "1"; } else { int nowTimes = Integer.parseInt(retryInfo.times); if (nowTimes >= maxRetryCount) { SharePatchFileUtil.safeDeleteFile(tempPatchFile); ShareTinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!"); return; } else { retryInfo.times = String.valueOf(nowTimes + 1); } } } else { copyToTempFile(patchFile); retryInfo = new RetryInfo(patchMd5, "1"); } RetryInfo.writeRetryProperty(retryInfoFile, retryInfo); } public boolean onPatchListenerCheck(String md5) { if (!isRetryEnable) { ShareTinkerLog.w(TAG, "onPatchListenerCheck retry disabled, just return"); return true; } if (!retryInfoFile.exists()) { ShareTinkerLog.w(TAG, "onPatchListenerCheck retry file is not exist, just return"); return true; } if (md5 == null) { ShareTinkerLog.w(TAG, "onPatchListenerCheck md5 is null, just return"); return true; } RetryInfo retryInfo = RetryInfo.readRetryProperty(retryInfoFile); if (md5.equals(retryInfo.md5)) { int nowTimes = Integer.parseInt(retryInfo.times); if (nowTimes >= maxRetryCount) { ShareTinkerLog.w(TAG, "onPatchListenerCheck, retry count %d must exceed than max retry count", nowTimes); SharePatchFileUtil.safeDeleteFile(tempPatchFile); return false; } } return true; } public boolean onPatchResetMaxCheck(String md5) { if (!isRetryEnable) { ShareTinkerLog.w(TAG, "onPatchResetMaxCheck retry disabled, just return"); return true; } if (!retryInfoFile.exists()) { ShareTinkerLog.w(TAG, "onPatchResetMaxCheck retry file is not exist, just return"); return true; } if (md5 == null) { ShareTinkerLog.w(TAG, "onPatchResetMaxCheck md5 is null, just return"); return true; } RetryInfo retryInfo = RetryInfo.readRetryProperty(retryInfoFile); if (md5.equals(retryInfo.md5)) { ShareTinkerLog.i(TAG, "onPatchResetMaxCheck, reset max check to 1"); retryInfo.times = "1"; RetryInfo.writeRetryProperty(retryInfoFile, retryInfo); } return true; } /** * if we receive any result, we can delete the temp retry info file */ public void onPatchServiceResult() { if (!isRetryEnable) { ShareTinkerLog.w(TAG, "onPatchServiceResult retry disabled, just return"); return; } //delete temp patch file if (tempPatchFile.exists()) { SharePatchFileUtil.safeDeleteFile(tempPatchFile); } } private void copyToTempFile(File patchFile) { if (patchFile.getAbsolutePath().equals(tempPatchFile.getAbsolutePath())) { return; } ShareTinkerLog.w(TAG, "try copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath()); try { SharePatchFileUtil.copyFileUsingStream(patchFile, tempPatchFile); } catch (IOException e) { ShareTinkerLog.e(TAG, "fail to copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath()); } } static class RetryInfo { String md5; String times; RetryInfo(String md5, String times) { this.md5 = md5; this.times = times; } static RetryInfo readRetryProperty(File infoFile) { String md5 = null; String times = null; Properties properties = new Properties(); FileInputStream inputStream = null; try { inputStream = new FileInputStream(infoFile); properties.load(inputStream); md5 = properties.getProperty(RETRY_FILE_MD5_PROPERTY); times = properties.getProperty(RETRY_COUNT_PROPERTY); } catch (IOException e) { ShareTinkerLog.e(TAG, "fail to readRetryProperty:" + e); } finally { IOHelper.closeQuietly(inputStream); } return new RetryInfo(md5, times); } static void writeRetryProperty(File infoFile, RetryInfo info) { if (info == null) { return; } File parentFile = infoFile.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } Properties newProperties = new Properties(); newProperties.put(RETRY_FILE_MD5_PROPERTY, info.md5); newProperties.put(RETRY_COUNT_PROPERTY, info.times); FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(infoFile, false); newProperties.store(outputStream, null); } catch (Exception e) { ShareTinkerLog.printErrStackTrace(TAG, e, "retry write property fail"); } finally { IOHelper.closeQuietly(outputStream); } } } } ================================================ FILE: tinker-android/tinker-android-lib/src/test/java/com/tencent/tinker/recover/ExampleUnitTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.recover; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-lib-no-op/build.gradle ================================================ apply plugin: 'com.android.library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion buildConfigField "String", "TINKER_VERSION", "\"${rootProject.ext.VERSION_NAME}\"" manifestPlaceholders = [TINKER_VERSION: "${rootProject.ext.VERSION_NAME}"] consumerProguardFiles file('../consumer-proguard.txt') } lintOptions { disable 'LongLogTag' } compileOptions { sourceCompatibility rootProject.ext.javaVersion targetCompatibility rootProject.ext.javaVersion } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' implementation project(':tinker-android:tinker-android-anno-support') api project(':tinker-android:tinker-android-loader-no-op') api project(':tinker-commons') } task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from("$buildDir/outputs/aar/") { include "${project.getName()}-release.aar" } into(rootProject.file("buildSdk/android/")) rename { String fileName -> fileName.replace("release", "${version}") } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-lib-no-op/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-lib-no-op POM_NAME=Tinker Android Lib ================================================ FILE: tinker-android/tinker-android-lib-no-op/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/androidTest/java/com/tencent/tinker/lib/patch/ApplicationTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/AndroidManifest.xml ================================================ ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/library/TinkerLoadLibrary.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.library; import android.content.Context; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.loader.TinkerRuntimeException; /** * Created by zhangshaowen on 17/1/5. * Thanks for Android Fragmentation */ public class TinkerLoadLibrary { public static void loadArmLibrary(Context context, String libName) { if (libName == null || libName.isEmpty() || context == null) { throw new TinkerRuntimeException("libName or context is null!"); } System.loadLibrary(libName); } public static void loadArmLibraryWithoutTinkerInstalled(ApplicationLike appLike, String libName) { if (libName == null || libName.isEmpty() || appLike == null) { throw new TinkerRuntimeException("libName or appLike is null!"); } System.loadLibrary(libName); } public static void loadArmV7Library(Context context, String libName) { if (libName == null || libName.isEmpty() || context == null) { throw new TinkerRuntimeException("libName or context is null!"); } System.loadLibrary(libName); } public static void loadArmV7LibraryWithoutTinkerInstalled(ApplicationLike appLike, String libName) { if (libName == null || libName.isEmpty() || appLike == null) { throw new TinkerRuntimeException("libName or appLike is null!"); } System.loadLibrary(libName); } public static boolean loadLibraryFromTinker(Context context, String relativePath, String libName) throws UnsatisfiedLinkError { return false; } public static boolean installNavitveLibraryABI(Context context, String currentABI) { return false; } public static boolean installNativeLibraryABIWithoutTinkerInstalled(ApplicationLike appLike, String currentABI) { return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.listener; import android.content.Context; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.loader.shareutil.ShareConstants; import java.io.File; /** * Created by zhangshaowen on 16/3/14. */ public class DefaultPatchListener implements PatchListener { protected final Context context; public DefaultPatchListener(Context context) { this.context = context; } @Override public int onPatchReceived(String path) { return checkPackageAndRunPatchService(path, false); } protected int checkPackageAndRunPatchService(String path, boolean useEmergencyMode) { final int returnCode = patchCheck(path, null); Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode); return returnCode; } protected int patchCheck(String path, String patchMd5) { return ShareConstants.ERROR_PATCH_DISABLE; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/PatchListener.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.listener; /** * Created by zhangshaowen on 16/3/14. */ public interface PatchListener { int onPatchReceived(String path); } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import com.tencent.tinker.lib.service.PatchResult; /** * Created by zhangshaowen on 16/3/15. */ public abstract class AbstractPatch { public abstract boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult); } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.patch; import android.content.Context; import com.tencent.tinker.lib.service.PatchResult; import com.tencent.tinker.lib.util.TinkerLog; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * generate new patch, you can implement your own patch processor class * Created by zhangshaowen on 16/3/14. */ public class UpgradePatch extends AbstractPatch { private static final String TAG = "Tinker.UpgradePatch"; @Override public boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/reporter/DefaultLoadReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Context; import java.io.File; /** * Created by zhangshaowen on 16/3/10. * the default implement for LoadReporter * you can extent it for your own work * all is running in the process which loading the patch */ public class DefaultLoadReporter implements LoadReporter { protected final Context context; public DefaultLoadReporter(Context context) { this.context = context; // Ignored. } @Override public void onLoadPatchListenerReceiveFail(File patchFile, int errorCode) { // Ignored. } @Override public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) { // Ignored. } @Override public void onLoadInterpret(int type, Throwable e) { // Ignored. } @Override public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) { // Ignored. } @Override public void onLoadFileMd5Mismatch(File file, int fileType) { // Ignored. } @Override public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) { // Ignored. } @Override public void onLoadResult(File patchDirectory, int loadCode, long cost) { // Ignored. } @Override public void onLoadException(Throwable e, int errorCode) { // Ignored. } @Override public void onLoadPackageCheckFail(File patchFile, int errorCode) { // Ignored. } public void checkAndCleanPatch() { // Ignored. } public boolean retryPatch() { return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/reporter/DefaultPatchReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Context; import android.content.Intent; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import java.io.File; import java.util.List; /** * Created by zhangshaowen on 16/3/14. */ public class DefaultPatchReporter implements PatchReporter { protected final Context context; public DefaultPatchReporter(Context context) { this.context = context; // Ignored. } @Override public void onPatchServiceStart(Intent intent) { // Ignored. } @Override public void onPatchPackageCheckFail(File patchFile, int errorCode) { // Ignored. } @Override public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) { // Ignored. } @Override public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) { // Ignored. } @Override public void onPatchDexOptFail(File patchFile, List dexFiles, Throwable t) { // Ignored. } @Override public void onPatchResult(File patchFile, boolean success, long cost) { // Ignored. } @Override public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) { // Ignored. } @Override public void onPatchException(File patchFile, Throwable e) { // Ignored. } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/reporter/LoadReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import java.io.File; /** * Created by zhangshaowen on 16/3/10. */ public interface LoadReporter { void onLoadPatchListenerReceiveFail(File patchFile, int errorCode); void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName); void onLoadInterpret(int type, Throwable e); void onLoadResult(File patchDirectory, int loadCode, long cost); void onLoadException(Throwable e, int errorCode); void onLoadFileNotFound(File file, int fileType, boolean isDirectory); void onLoadFileMd5Mismatch(File file, int fileType); void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile); void onLoadPackageCheckFail(File patchFile, int errorCode); } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/reporter/PatchReporter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.reporter; import android.content.Intent; import com.tencent.tinker.lib.patch.UpgradePatch; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import java.io.File; import java.util.List; /** * Created by zhangshaowen on 16/3/14. * * means that it is a newly patch, we would default use {@link UpgradePatch} * to do the job */ public interface PatchReporter { void onPatchServiceStart(Intent intent); void onPatchPackageCheckFail(File patchFile, int errorCode); void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion); void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType); void onPatchDexOptFail(File patchFile, List dexFiles, Throwable t); void onPatchResult(File patchFile, boolean success, long cost); void onPatchException(File patchFile, Throwable e); void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion); } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/service/AbstractResultService.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import android.app.IntentService; import android.content.Context; import android.content.Intent; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/14. */ public abstract class AbstractResultService extends IntentService { private static final String TAG = "Tinker.AbstractResultService"; public AbstractResultService() { super("TinkerResultService"); } public static void runResultService(Context context, PatchResult result, String resultServiceClass) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } @Override protected void onHandleIntent(Intent intent) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public abstract void onPatchResult(PatchResult result); } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/service/DefaultTinkerResultService.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by zhangshaowen on 16/3/19. */ public class DefaultTinkerResultService extends AbstractResultService { private static final String TAG = "Tinker.DefaultTinkerResultService"; @Override public void onPatchResult(PatchResult result) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public void deleteRawPatchFile(File rawFile) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public boolean checkIfNeedKill(PatchResult result) { return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/service/PatchResult.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.service; import java.io.Serializable; /** * Created by zhangshaowen on 16/3/19. */ public class PatchResult implements Serializable { public boolean isSuccess; public String rawPatchFilePath; public long costTime; public long dexoptTriggerTime; public boolean isOatGenerated; public Throwable e; //@Nullable public String patchVersion; @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("\nPatchResult: \n"); sb.append("isSuccess:" + isSuccess + "\n"); sb.append("rawPatchFilePath:" + rawPatchFilePath + "\n"); sb.append("costTime:" + costTime + "\n"); sb.append("dexoptTriggerTime:" + dexoptTriggerTime + "\n"); sb.append("isOatGenerated:" + isOatGenerated + "\n"); if (patchVersion != null) { sb.append("patchVersion:" + patchVersion + "\n"); } if (e != null) { sb.append("Throwable:" + e.getMessage() + "\n"); } return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import android.content.Intent; import com.tencent.tinker.lib.listener.DefaultPatchListener; import com.tencent.tinker.lib.listener.PatchListener; import com.tencent.tinker.lib.patch.AbstractPatch; import com.tencent.tinker.lib.patch.UpgradePatch; import com.tencent.tinker.lib.reporter.DefaultLoadReporter; import com.tencent.tinker.lib.reporter.DefaultPatchReporter; import com.tencent.tinker.lib.reporter.LoadReporter; import com.tencent.tinker.lib.reporter.PatchReporter; import com.tencent.tinker.lib.service.AbstractResultService; import com.tencent.tinker.lib.service.DefaultTinkerResultService; import com.tencent.tinker.lib.util.TinkerServiceInternals; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by zhangshaowen on 16/3/10. */ public class Tinker { private static final String TAG = "Tinker.Tinker"; private static Tinker sInstance; private static boolean sInstalled = false; final Context context; /** * data dir, such as /data/data/tinker.sample.android/tinker */ final File patchDirectory; final PatchListener listener; final LoadReporter loadReporter; final PatchReporter patchReporter; final File patchInfoFile; final File patchInfoLockFile; final boolean isMainProcess; final boolean isPatchProcess; /** * same with {@code TinkerApplication.tinkerLoadVerifyFlag} */ final boolean tinkerLoadVerifyFlag; /** * same with {@code TinkerApplication.tinkerFlags} */ int tinkerFlags; TinkerLoadResult tinkerLoadResult; /** * whether load patch success */ private boolean loaded = false; private Tinker(Context context, int tinkerFlags, LoadReporter loadReporter, PatchReporter patchReporter, PatchListener listener, File patchDirectory, File patchInfoFile, File patchInfoLockFile, boolean isInMainProc, boolean isPatchProcess, boolean tinkerLoadVerifyFlag) { this.context = context; this.listener = listener; this.loadReporter = loadReporter; this.patchReporter = patchReporter; this.tinkerFlags = tinkerFlags; this.patchDirectory = patchDirectory; this.patchInfoFile = patchInfoFile; this.patchInfoLockFile = patchInfoLockFile; this.isMainProcess = isInMainProc; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.isPatchProcess = isPatchProcess; } /** * init with default config tinker * for safer, you must use @{link TinkerInstaller.install} first! * * @param context we will use the application context * @return the Tinker object */ public static Tinker with(Context context) { if (!sInstalled) { throw new TinkerRuntimeException("you must install tinker before get tinker sInstance"); } synchronized (Tinker.class) { if (sInstance == null) { sInstance = new Builder(context).build(); } } return sInstance; } /** * create custom tinker by {@link Tinker.Builder} * please do it when very first your app start. * * @param tinker */ public static void create(Tinker tinker) { if (sInstance != null) { throw new TinkerRuntimeException("Tinker instance is already set."); } sInstance = tinker; } public static boolean isTinkerInstalled() { return sInstalled; } /** * you must install tinker first!! * * @param intentResult * @param serviceClass * @param upgradePatch */ public void install(Intent intentResult, Class serviceClass, AbstractPatch upgradePatch) { sInstalled = true; ShareTinkerLog.e(TAG, "[-] Tinker is disabled since I'm no-op version."); } /** * set tinkerPatchServiceNotificationId * * @param id */ public void setPatchServiceNotificationId(int id) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } /** * Nullable, should check the loaded flag first */ public TinkerLoadResult getTinkerLoadResultIfPresent() { return tinkerLoadResult; } public void install(Intent intentResult) { install(intentResult, DefaultTinkerResultService.class, new UpgradePatch()); } public Context getContext() { return context; } public boolean isMainProcess() { return isMainProcess; } public boolean isPatchProcess() { return isPatchProcess; } public void setTinkerDisable() { tinkerFlags = ShareConstants.TINKER_DISABLE; } public LoadReporter getLoadReporter() { return loadReporter; } public PatchReporter getPatchReporter() { return patchReporter; } public boolean isTinkerEnabled() { return false; } public boolean isTinkerLoaded() { return false; } public void setTinkerLoaded(boolean isLoaded) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public boolean isTinkerLoadVerify() { return tinkerLoadVerifyFlag; } public boolean isEnabledForDex() { return false; } public boolean isEnabledForNativeLib() { return false; } public boolean isEnabledForResource() { return false; } public File getPatchDirectory() { return patchDirectory; } public File getPatchInfoFile() { return patchInfoFile; } public File getPatchInfoLockFile() { return patchInfoLockFile; } public PatchListener getPatchListener() { return listener; } public int getTinkerFlags() { return tinkerFlags; } /** * clean all patch files */ public void cleanPatch() { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } /** * rollback patch should restart all process */ public void rollbackPatch() { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } /** * clean the patch version files, such as tinker/patch-641e634c * * @param versionName */ public void cleanPatchByVersion(String versionName) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } /** * get the rom size of tinker, use kb * * @return */ public long getTinkerRomSpace() { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); return 0; } /** * try delete the temp version files * * @param patchApk */ public void cleanPatchByPatchApk(File patchApk) { if (patchDirectory == null || patchApk == null || !patchApk.exists()) { return; } String versionName = SharePatchFileUtil.getPatchVersionDirectory(SharePatchFileUtil.getMD5(patchApk)); cleanPatchByVersion(versionName); } public static class Builder { private final Context context; private final boolean mainProcess; private final boolean patchProcess; private int status = -1; private LoadReporter loadReporter; private PatchReporter patchReporter; private PatchListener listener; private File patchDirectory; private File patchInfoFile; private File patchInfoLockFile; private Boolean tinkerLoadVerifyFlag; /** * Start building a new {@link Tinker} instance. */ public Builder(Context context) { if (context == null) { throw new TinkerRuntimeException("Context must not be null."); } this.context = context; this.mainProcess = TinkerServiceInternals.isInMainProcess(context); this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context); this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context); if (this.patchDirectory == null) { ShareTinkerLog.e(TAG, "patchDirectory is null!"); return; } this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath()); this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath()); ShareTinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory); } public Builder tinkerFlags(int tinkerFlags) { if (this.status != -1) { throw new TinkerRuntimeException("tinkerFlag is already set."); } this.status = tinkerFlags; return this; } public Builder tinkerLoadVerifyFlag(Boolean verifyMd5WhenLoad) { if (verifyMd5WhenLoad == null) { throw new TinkerRuntimeException("tinkerLoadVerifyFlag must not be null."); } if (this.tinkerLoadVerifyFlag != null) { throw new TinkerRuntimeException("tinkerLoadVerifyFlag is already set."); } this.tinkerLoadVerifyFlag = verifyMd5WhenLoad; return this; } public Builder loadReport(LoadReporter loadReporter) { if (loadReporter == null) { throw new TinkerRuntimeException("loadReporter must not be null."); } if (this.loadReporter != null) { throw new TinkerRuntimeException("loadReporter is already set."); } this.loadReporter = loadReporter; return this; } public Builder patchReporter(PatchReporter patchReporter) { if (patchReporter == null) { throw new TinkerRuntimeException("patchReporter must not be null."); } if (this.patchReporter != null) { throw new TinkerRuntimeException("patchReporter is already set."); } this.patchReporter = patchReporter; return this; } public Builder listener(PatchListener listener) { if (listener == null) { throw new TinkerRuntimeException("listener must not be null."); } if (this.listener != null) { throw new TinkerRuntimeException("listener is already set."); } this.listener = listener; return this; } public Tinker build() { if (status == -1) { status = ShareConstants.TINKER_ENABLE_ALL; } if (loadReporter == null) { loadReporter = new DefaultLoadReporter(context); } if (patchReporter == null) { patchReporter = new DefaultPatchReporter(context); } if (listener == null) { listener = new DefaultPatchListener(context); } if (tinkerLoadVerifyFlag == null) { tinkerLoadVerifyFlag = false; } return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory, patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag); } } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/tinker/TinkerApplicationHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.util.HashMap; /** * sometimes, you may want to install tinker later, or never install tinker in some process. * you can use {@code TinkerApplicationHelper} API to get the tinker status! * Created by zhangshaowen on 16/6/28. */ public class TinkerApplicationHelper { private static final String TAG = "Tinker.TinkerApplicationHelper"; public static boolean isTinkerEnableAll(ApplicationLike applicationLike) { return false; } public static boolean isTinkerEnableForDex(ApplicationLike applicationLike) { return false; } public static boolean isTinkerEnableForNativeLib(ApplicationLike applicationLike) { return false; } public static boolean isTinkerEnableForResource(ApplicationLike applicationLike) { return false; } public static File getTinkerPatchDirectory(ApplicationLike applicationLike) { return null; } public static boolean isTinkerLoadSuccess(ApplicationLike applicationLike) { return false; } public static HashMap getLoadDexesAndMd5(ApplicationLike applicationLike) { return null; } public static HashMap getLoadLibraryAndMd5(ApplicationLike applicationLike) { return null; } public static HashMap getPackageConfigs(ApplicationLike applicationLike) { return null; } public static String getCurrentVersion(ApplicationLike applicationLike) { return null; } public static void cleanPatch(ApplicationLike applicationLike) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public static void loadArmV7aLibrary(ApplicationLike applicationLike, String libName) { System.loadLibrary(libName); } public static void loadArmLibrary(ApplicationLike applicationLike, String libName) { if (libName == null || libName.isEmpty() || applicationLike == null) { throw new TinkerRuntimeException("libName or context is null!"); } System.loadLibrary(libName); } public static boolean loadLibraryFromTinker(ApplicationLike applicationLike, String relativePath, String libname) throws UnsatisfiedLinkError { return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/tinker/TinkerInstaller.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.lib.listener.PatchListener; import com.tencent.tinker.lib.patch.AbstractPatch; import com.tencent.tinker.lib.reporter.LoadReporter; import com.tencent.tinker.lib.reporter.PatchReporter; import com.tencent.tinker.lib.service.AbstractResultService; import com.tencent.tinker.lib.util.TinkerLog; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/19. */ public class TinkerInstaller { private static final String TAG = "Tinker.TinkerInstaller"; /** * install tinker with default config, you must install tinker before you use their api * or you can just use {@link TinkerApplicationHelper}'s api * * @param applicationLike */ public static Tinker install(ApplicationLike applicationLike) { Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent()); return tinker; } /** * install tinker with custom config, you must install tinker before you use their api * or you can just use {@link TinkerApplicationHelper}'s api * * @param applicationLike * @param loadReporter * @param patchReporter * @param listener * @param resultServiceClass * @param upgradePatchProcessor */ public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter, PatchListener listener, Class resultServiceClass, AbstractPatch upgradePatchProcessor) { Tinker tinker = new Tinker.Builder(applicationLike.getApplication()) .tinkerFlags(applicationLike.getTinkerFlags()) .loadReport(loadReporter) .listener(listener) .patchReporter(patchReporter) .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build(); Tinker.create(tinker); tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor); return tinker; } public static void cleanPatch(Context context) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public static void onReceiveUpgradePatch(Context context, String patchLocation) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } /** * set logIml for TinkerLog * * @param imp */ public static void setLogIml(ShareTinkerLog.TinkerLogImp imp) { ShareTinkerLog.setTinkerLogImp(imp); } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/tinker/TinkerLoadResult.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.tinker; import android.content.Context; import android.content.Intent; import android.os.Build; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.util.HashMap; /** * Created by zhangshaowen on 16/3/25. */ public class TinkerLoadResult { private static final String TAG = "Tinker.TinkerLoadResult"; //@Nullable public SharePatchInfo patchInfo; //@Nullable public String currentVersion; //@Nullable public String oatDir; public boolean versionChanged; public boolean useInterpretMode; public boolean systemOTA; //@Nullable public File patchVersionDirectory; //@Nullable public File patchVersionFile; //@Nullable public File dexDirectory; //@Nullable public File libraryDirectory; //@Nullable public File resourceDirectory; //@Nullable public File resourceFile; //@Nullable public HashMap dexes; //@Nullable public HashMap libs; //@Nullable public HashMap packageConfig; public int loadCode; public long costTime; public boolean parseTinkerResult(Context context, Intent intentResult) { Tinker tinker = Tinker.with(context); loadCode = ShareIntentUtil.getIntentReturnCode(intentResult); costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult); systemOTA = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, false); oatDir = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OAT_DIR); useInterpretMode = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH.equals(oatDir); final boolean isMainProcess = tinker.isMainProcess(); ShareTinkerLog.i(TAG, "parseTinkerResult loadCode:%d, process name:%s, main process:%b, systemOTA:%b, fingerPrint:%s, oatDir:%s, useInterpretMode:%b", loadCode, ShareTinkerInternals.getProcessName(context), isMainProcess, systemOTA, Build.FINGERPRINT, oatDir, useInterpretMode); //@Nullable final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION); //@Nullable final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION); final File patchDirectory = tinker.getPatchDirectory(); final File patchInfoFile = tinker.getPatchInfoFile(); if (oldVersion != null && newVersion != null) { if (isMainProcess) { currentVersion = newVersion; } else { currentVersion = oldVersion; } ShareTinkerLog.i(TAG, "parseTinkerResult oldVersion:%s, newVersion:%s, current:%s", oldVersion, newVersion, currentVersion); //current version may be nil String patchName = SharePatchFileUtil.getPatchVersionDirectory(currentVersion); if (!ShareTinkerInternals.isNullOrNil(patchName)) { patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + patchName); patchVersionFile = new File(patchVersionDirectory.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(currentVersion)); dexDirectory = new File(patchVersionDirectory, ShareConstants.DEX_PATH); libraryDirectory = new File(patchVersionDirectory, ShareConstants.SO_PATH); resourceDirectory = new File(patchVersionDirectory, ShareConstants.RES_PATH); resourceFile = new File(resourceDirectory, ShareConstants.RES_NAME); } final boolean isProtectedApp = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_IS_PROTECTED_APP, false); patchInfo = new SharePatchInfo(oldVersion, newVersion, isProtectedApp, false, Build.FINGERPRINT, oatDir, false); versionChanged = !(oldVersion.equals(newVersion)); } //found uncaught exception, just return Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult); if (exception != null) { ShareTinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode); int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; switch (loadCode) { case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN; break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX; break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE; break; case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION: errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT; break; default: break; } tinker.getLoadReporter().onLoadException(exception, errorCode); return false; } switch (loadCode) { case ShareConstants.ERROR_LOAD_GET_INTENT_FAIL: ShareTinkerLog.e(TAG, "can't get the right intent return code"); throw new TinkerRuntimeException("can't get the right intent return code"); case ShareConstants.ERROR_LOAD_DISABLE: ShareTinkerLog.w(TAG, "tinker is disable, just return"); break; // case ShareConstants.ERROR_LOAD_PATCH_NOT_SUPPORTED: // ShareTinkerLog.w(TAG, "tinker is not supported, just return"); // break; case ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST: case ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST: ShareTinkerLog.w(TAG, "can't find patch file, is ok, just return"); break; case ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED: ShareTinkerLog.e(TAG, "path info corrupted"); tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); break; case ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK: ShareTinkerLog.e(TAG, "path info blank, wait main process to restart"); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST: ShareTinkerLog.e(TAG, "patch version directory not found, current version:%s", currentVersion); tinker.getLoadReporter().onLoadFileNotFound(patchVersionDirectory, ShareConstants.TYPE_PATCH_FILE, true); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST: ShareTinkerLog.e(TAG, "patch version file not found, current version:%s", currentVersion); if (patchVersionFile == null) { throw new TinkerRuntimeException("error load patch version file not exist, but file is null"); } tinker.getLoadReporter().onLoadFileNotFound(patchVersionFile, ShareConstants.TYPE_PATCH_FILE, false); break; case ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL: ShareTinkerLog.i(TAG, "patch package check fail"); if (patchVersionFile == null) { throw new TinkerRuntimeException("error patch package check fail , but file is null"); } int errorCode = intentResult.getIntExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); tinker.getLoadReporter().onLoadPackageCheckFail(patchVersionFile, errorCode); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST: if (dexDirectory != null) { ShareTinkerLog.e(TAG, "patch dex file directory not found:%s", dexDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(dexDirectory, ShareConstants.TYPE_DEX, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch dex file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch dex file directory not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST: String dexPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); if (dexPath != null) { //we only pass one missing file ShareTinkerLog.e(TAG, "patch dex file not found:%s", dexPath); tinker.getLoadReporter().onLoadFileNotFound(new File(dexPath), ShareConstants.TYPE_DEX, false); } else { ShareTinkerLog.e(TAG, "patch dex file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch dex file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_DEX, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST: String dexOptPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH); if (dexOptPath != null) { //we only pass one missing file ShareTinkerLog.e(TAG, "patch dex opt file not found:%s", dexOptPath); tinker.getLoadReporter().onLoadFileNotFound(new File(dexOptPath), ShareConstants.TYPE_DEX_OPT, false); } else { ShareTinkerLog.e(TAG, "patch dex opt file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch dex opt file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_DEX, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch lib file directory not found:%s", libraryDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(libraryDirectory, ShareConstants.TYPE_LIBRARY, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch lib file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch lib file directory not found, warning why the path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_LIBRARY, true); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST: String libPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH); if (libPath != null) { //we only pass one missing file and then we break ShareTinkerLog.e(TAG, "patch lib file not found:%s", libPath); tinker.getLoadReporter().onLoadFileNotFound(new File(libPath), ShareConstants.TYPE_LIBRARY, false); } else { ShareTinkerLog.e(TAG, "patch lib file not found, but path is null!!!!"); throw new TinkerRuntimeException("patch lib file not found, but path is null!!!!"); // tinker.getLoadReporter().onLoadFileNotFound(null, // ShareConstants.TYPE_LIBRARY, false); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL: ShareTinkerLog.e(TAG, "patch dex load fail, classloader is null"); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH: String mismatchPath = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH); if (mismatchPath == null) { ShareTinkerLog.e(TAG, "patch dex file md5 is mismatch, but path is null!!!!"); throw new TinkerRuntimeException("patch dex file md5 is mismatch, but path is null!!!!"); } else { ShareTinkerLog.e(TAG, "patch dex file md5 is mismatch: %s", mismatchPath); tinker.getLoadReporter().onLoadFileMd5Mismatch(new File(mismatchPath), ShareConstants.TYPE_DEX); } break; case ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL: ShareTinkerLog.i(TAG, "rewrite patch info file corrupted"); tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile); break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch resource file directory not found:%s", resourceDirectory.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(resourceDirectory, ShareConstants.TYPE_RESOURCE, true); } else { //should be not here ShareTinkerLog.e(TAG, "patch resource file directory not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch resource file directory not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST: if (patchVersionDirectory != null) { ShareTinkerLog.e(TAG, "patch resource file not found:%s", resourceFile.getAbsolutePath()); tinker.getLoadReporter().onLoadFileNotFound(resourceFile, ShareConstants.TYPE_RESOURCE, false); } else { //should be not here ShareTinkerLog.e(TAG, "patch resource file not found, warning why the path is null!!!!"); throw new TinkerRuntimeException("patch resource file not found, warning why the path is null!!!!"); } break; case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH: if (resourceFile == null) { ShareTinkerLog.e(TAG, "resource file md5 mismatch, but patch resource file not found!"); throw new TinkerRuntimeException("resource file md5 mismatch, but patch resource file not found!"); } ShareTinkerLog.e(TAG, "patch resource file md5 is mismatch: %s", resourceFile.getAbsolutePath()); tinker.getLoadReporter().onLoadFileMd5Mismatch(resourceFile, ShareConstants.TYPE_RESOURCE); break; case ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION: tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult)); break; case ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION: tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_COMMAND_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult)); break; case ShareConstants.ERROR_LOAD_OK: ShareTinkerLog.i(TAG, "oh yeah, tinker load all success"); tinker.setTinkerLoaded(true); // get load dex dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult); libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult); packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult); if (useInterpretMode) { tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_OK, null); } if (isMainProcess && versionChanged) { //change the old version to new tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName()); } return true; default: break; } return false; } public String getPackageConfigByName(String name) { return null; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/util/TinkerLog.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/17. * * DEPRECATED. Use {@link com.tencent.tinker.loader.shareutil.ShareTinkerLog} instead. */ @Deprecated public class TinkerLog { private static final String TAG = "Tinker.TinkerLog"; public static void setTinkerLogImp(TinkerLogImp imp) { ShareTinkerLog.setTinkerLogImp(imp); } public static ShareTinkerLog.TinkerLogImp getImpl() { return ShareTinkerLog.getImpl(); } public static void v(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void e(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void w(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void i(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void d(final String tag, final String msg, final Object... obj) { ShareTinkerLog.v(tag, msg, obj); } public static void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj) { ShareTinkerLog.printErrStackTrace(tag, tr, format, obj); } public static void printPendingLogs() { ShareTinkerLog.printPendingLogs(); } public interface TinkerLogImp extends ShareTinkerLog.TinkerLogImp {} } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/util/TinkerServiceInternals.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import android.content.Context; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/3/10. */ public class TinkerServiceInternals extends ShareTinkerInternals { private static final String TAG = "Tinker.ServiceInternals"; public static void killTinkerPatchServiceProcess(Context context) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public static boolean isTinkerPatchServiceRunning(Context context) { return false; } public static String getTinkerPatchServiceName(final Context context) { return null; } public static boolean isInTinkerPatchServiceProcess(Context context) { return false; } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/util/UpgradePatchRetry.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.lib.util; import android.content.Context; import android.content.Intent; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by zhangshaowen on 16/7/3. */ public class UpgradePatchRetry { private static final String TAG = "Tinker.UpgradePatchRetry"; private static UpgradePatchRetry sInstance; public UpgradePatchRetry(Context context) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public static UpgradePatchRetry getInstance(Context context) { if (sInstance == null) { sInstance = new UpgradePatchRetry(context); } return sInstance; } public void setRetryEnable(boolean enable) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public void setMaxRetryCount(int count) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public boolean onPatchRetryLoad() { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); return false; } public void onPatchServiceStart(Intent intent) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } public boolean onPatchListenerCheck(String md5) { return true; } public boolean onPatchResetMaxCheck(String md5) { return true; } public void onPatchServiceResult() { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); } } ================================================ FILE: tinker-android/tinker-android-lib-no-op/src/test/java/com/tencent/tinker/recover/ExampleUnitTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.recover; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: tinker-android/tinker-android-loader/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-loader/build.gradle ================================================ apply plugin: 'com.android.library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion buildConfigField "String", "TINKER_VERSION", "\"${rootProject.ext.VERSION_NAME}\"" manifestPlaceholders = [TINKER_VERSION: "${rootProject.ext.VERSION_NAME}"] consumerProguardFiles file('../consumer-proguard.txt') } lintOptions { disable 'LongLogTag' } compileOptions { sourceCompatibility rootProject.ext.javaVersion targetCompatibility rootProject.ext.javaVersion } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) compileOnly fileTree(dir: 'stubs', include: ['*.jar']) testImplementation 'junit:junit:4.12' implementation project(':tinker-android:tinker-android-anno-support') } task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from("$buildDir/outputs/aar/") { include "${project.getName()}-release.aar" } into(rootProject.file("buildSdk/android/")) rename { String fileName -> fileName.replace("release", "${version}") } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-loader/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-loader POM_NAME=Tinker Android Loader POM_PACKAGING=jar ================================================ FILE: tinker-android/tinker-android-loader/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: tinker-android/tinker-android-loader/src/androidTest/java/com/tencent/tinker/loader/ApplicationTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/AndroidManifest.xml ================================================ ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AbstractTinkerLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Intent; import com.tencent.tinker.loader.app.TinkerApplication; /** * Created by zhangshaowen on 16/4/30. */ public abstract class AbstractTinkerLoader { abstract public Intent tryLoad(TinkerApplication app); } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/AppInfoChangedBlocker.java ================================================ package com.tencent.tinker.loader; import android.app.Application; import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.Process; import android.util.Log; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.Field; /** * Created by tangyinsheng on 2020/5/10. *

* Some situations may cause our resource modification to be ineffective, * for example, an APPLICATION_INFO_CHANGED message will reset LoadedApk#mResDir * to default value, then a relaunch activity which using tinker resources may * throw an Resources$NotFoundException. *

* Monitor and handle them. *

* */ public final class AppInfoChangedBlocker { private static final String TAG = "Tinker.AppInfoChangedBlocker"; public static boolean tryStart(Application app) { if (Build.VERSION.SDK_INT < 26) { Log.i(TAG, "tryStart: SDK_INT is less than 26, skip rest logic."); return true; } try { ShareTinkerLog.i(TAG, "tryStart called."); interceptHandler(fetchMHObject(app)); ShareTinkerLog.i(TAG, "tryStart done."); return true; } catch (Throwable e) { ShareTinkerLog.e(TAG, "AppInfoChangedBlocker start failed, simply ignore.", e); return false; } } private static Handler fetchMHObject(Context context) throws Exception { final Object activityThread = ShareReflectUtil.getActivityThread(context, null); final Field mHField = ShareReflectUtil.findField(activityThread, "mH"); return (Handler) mHField.get(activityThread); } private static void interceptHandler(Handler mH) throws Exception { final Field mCallbackField = ShareReflectUtil.findField(Handler.class, "mCallback"); final Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH); if (!(originCallback instanceof HackerCallback)) { HackerCallback hackerCallback = new HackerCallback(originCallback, mH.getClass()); mCallbackField.set(mH, hackerCallback); } else { ShareTinkerLog.w(TAG, "Already intercepted, skip rest logic."); } } private static class HackerCallback implements Handler.Callback { private final int APPLICATION_INFO_CHANGED; private Handler.Callback origin; HackerCallback(Handler.Callback ori, Class $H) { this.origin = ori; int appInfoChanged; try { appInfoChanged = ShareReflectUtil.findField($H, "APPLICATION_INFO_CHANGED").getInt(null); } catch (Throwable e) { appInfoChanged = 156; // default value } APPLICATION_INFO_CHANGED = appInfoChanged; } @Override public boolean handleMessage(Message msg) { boolean consume = false; if (hackMessage(msg)) { consume = true; } else if (origin != null) { consume = origin.handleMessage(msg); } return consume; } private boolean hackMessage(Message msg) { if (msg.what == APPLICATION_INFO_CHANGED) { // We are generally in the background this moment(signal trigger is // in front of user), and the signal was going to relaunch all our // activities to apply new overlay resources. So we could simply kill // ourselves, or ignore this signal, or reload tinker resources. ShareTinkerLog.w(TAG, "Suicide now."); Process.killProcess(Process.myPid()); return true; } return false; } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/NewClassLoaderInjector.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.annotation.SuppressLint; import android.app.Application; import android.content.Context; import android.content.res.Resources; import android.os.Build; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.io.File; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; import dalvik.system.DelegateLastClassLoader; /** * Created by tangyinsheng on 2019-10-31. */ final class NewClassLoaderInjector { public static ClassLoader inject(Application app, ClassLoader oldClassLoader, File dexOptDir, boolean useDLC, List patchedDexes) throws Throwable { final String[] patchedDexPaths = new String[patchedDexes.size()]; for (int i = 0; i < patchedDexPaths.length; ++i) { patchedDexPaths[i] = patchedDexes.get(i).getAbsolutePath(); } final ClassLoader newClassLoader = createNewClassLoader(oldClassLoader, dexOptDir, useDLC, true, patchedDexPaths); doInject(app, newClassLoader); return newClassLoader; } public static ClassLoader triggerDex2Oat(Context context, File dexOptDir, boolean useDLC, String... dexPaths) throws Throwable { return createNewClassLoader(context.getClassLoader(), dexOptDir, useDLC, false, dexPaths); } @SuppressLint("NewApi") @SuppressWarnings("unchecked") private static ClassLoader createNewClassLoader(ClassLoader oldClassLoader, File dexOptDir, boolean useDLC, boolean forActualLoading, String... patchDexPaths) throws Throwable { final Field pathListField = findField( Class.forName("dalvik.system.BaseDexClassLoader", false, oldClassLoader), "pathList"); final Object oldPathList = pathListField.get(oldClassLoader); final StringBuilder dexPathBuilder = new StringBuilder(); final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0; if (hasPatchDexPaths) { for (int i = 0; i < patchDexPaths.length; ++i) { if (i > 0) { dexPathBuilder.append(File.pathSeparator); } dexPathBuilder.append(patchDexPaths[i]); } } final String combinedDexPath = dexPathBuilder.toString(); final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories"); List oldNativeLibraryDirectories = null; if (nativeLibraryDirectoriesField.getType().isArray()) { oldNativeLibraryDirectories = Arrays.asList((File[]) nativeLibraryDirectoriesField.get(oldPathList)); } else { oldNativeLibraryDirectories = (List) nativeLibraryDirectoriesField.get(oldPathList); } final StringBuilder libraryPathBuilder = new StringBuilder(); boolean isFirstItem = true; for (File libDir : oldNativeLibraryDirectories) { if (libDir == null) { continue; } if (isFirstItem) { isFirstItem = false; } else { libraryPathBuilder.append(File.pathSeparator); } libraryPathBuilder.append(libDir.getAbsolutePath()); } final String combinedLibraryPath = libraryPathBuilder.toString(); ClassLoader result = null; if (useDLC && ShareTinkerInternals.isNewerOrEqualThanVersion(27, true)) { if (ShareTinkerInternals.isNewerOrEqualThanVersion(31, true)) { result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, oldClassLoader); } else { result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader()); final Field parentField = ClassLoader.class.getDeclaredField("parent"); parentField.setAccessible(true); parentField.set(result, oldClassLoader); } } else { result = new TinkerClassLoader(combinedDexPath, dexOptDir, combinedLibraryPath, oldClassLoader); } // 'EnsureSameClassLoader' mechanism which is first introduced in Android O // may cause exception if we replace definingContext of old classloader. if (forActualLoading && !ShareTinkerInternals.isNewerOrEqualThanVersion(26, true)) { findField(oldPathList.getClass(), "definingContext").set(oldPathList, result); } return result; } private static void doInject(Application app, ClassLoader classLoader) throws Throwable { Thread.currentThread().setContextClassLoader(classLoader); final Context baseContext = (Context) findField(app.getClass(), "mBase").get(app); try { findField(baseContext.getClass(), "mClassLoader").set(baseContext, classLoader); } catch (Throwable ignored) { // There's no mClassLoader field in ContextImpl before Android O. // However we should try our best to replace this field in case some // customized system has one. } final Object basePackageInfo = findField(baseContext.getClass(), "mPackageInfo").get(baseContext); findField(basePackageInfo.getClass(), "mClassLoader").set(basePackageInfo, classLoader); final Resources res = app.getResources(); try { findField(res.getClass(), "mClassLoader").set(res, classLoader); } catch (Throwable ignored) { // Ignored. } try { final Object drawableInflater = findField(res.getClass(), "mDrawableInflater").get(res); if (drawableInflater != null) { findField(drawableInflater.getClass(), "mClassLoader").set(drawableInflater, classLoader); } } catch (Throwable ignored) { // Ignored. } } private static Field findField(Class clazz, String name) throws Throwable { Class currClazz = clazz; while (true) { try { final Field result = currClazz.getDeclaredField(name); result.setAccessible(true); return result; } catch (Throwable ignored) { if (currClazz == Object.class) { throw new NoSuchFieldException("Cannot find field " + name + " in class " + clazz.getName() + " and its super classes."); } else { currClazz = currClazz.getSuperclass(); } } } } private NewClassLoaderInjector() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/SystemClassLoaderAdder.java ================================================ /* * Copyright (C) 2016 THL A29 Limited, a Tencent company. * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.app.Application; import android.os.Build; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.zip.ZipFile; import dalvik.system.DexFile; import dalvik.system.PathClassLoader; /** * Created by zhangshaowen on 16/3/18. */ public class SystemClassLoaderAdder { public static final String CHECK_DEX_CLASS = "com.tencent.tinker.loader.TinkerTestDexLoad"; public static final String CHECK_DEX_FIELD = "isPatch"; private static final String TAG = "Tinker.ClassLoaderAdder"; private static int sPatchDexCount = 0; public static void installDexes(Application application, ClassLoader loader, File dexOptDir, List files, boolean isProtectedApp, boolean useDLC) throws Throwable { ShareTinkerLog.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size()); if (!files.isEmpty()) { files = createSortedAdditionalPathEntries(files); ClassLoader classLoader = loader; if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) { classLoader = NewClassLoaderInjector.inject(application, loader, dexOptDir, useDLC, files); } else { injectDexesInternal(classLoader, files, dexOptDir); } //install done sPatchDexCount = files.size(); ShareTinkerLog.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount); if (!checkDexInstall(classLoader)) { //reset patch dex SystemClassLoaderAdder.uninstallPatchDex(classLoader); throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL); } } } static void injectDexesInternal(ClassLoader cl, List dexFiles, File optimizeDir) throws Throwable { if (Build.VERSION.SDK_INT >= 23) { V23.install(cl, dexFiles, optimizeDir); } else if (Build.VERSION.SDK_INT >= 19) { V19.install(cl, dexFiles, optimizeDir); } else if (Build.VERSION.SDK_INT >= 14) { V14.install(cl, dexFiles, optimizeDir); } else { V4.install(cl, dexFiles, optimizeDir); } } public static void installApk(PathClassLoader loader, List files) throws Throwable { if (!files.isEmpty()) { files = createSortedAdditionalPathEntries(files); ClassLoader classLoader = loader; ArkHot.install(classLoader, files); sPatchDexCount = files.size(); ShareTinkerLog.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount); if (!checkDexInstall(classLoader)) { // reset patch dex // SystemClassLoaderAdder.uninstallPatchDex(classLoader); // throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL); } } } public static void uninstallPatchDex(ClassLoader classLoader) throws Throwable { if (sPatchDexCount <= 0) { return; } if (Build.VERSION.SDK_INT >= 14) { Field pathListField = ShareReflectUtil.findField(classLoader, "pathList"); Object dexPathList = pathListField.get(classLoader); ShareReflectUtil.reduceFieldArray(dexPathList, "dexElements", sPatchDexCount); } else { ShareReflectUtil.reduceFieldArray(classLoader, "mPaths", sPatchDexCount); ShareReflectUtil.reduceFieldArray(classLoader, "mFiles", sPatchDexCount); ShareReflectUtil.reduceFieldArray(classLoader, "mZips", sPatchDexCount); try { ShareReflectUtil.reduceFieldArray(classLoader, "mDexs", sPatchDexCount); } catch (Exception e) { // Ignored. } } } private static boolean checkDexInstall(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class clazz = Class.forName(CHECK_DEX_CLASS, true, classLoader); Field filed = ShareReflectUtil.findField(clazz, CHECK_DEX_FIELD); boolean isPatch = (boolean) filed.get(null); ShareTinkerLog.i(TAG, "checkDexInstall result: %s, checker_classloader: %s", isPatch, clazz.getClassLoader()); return isPatch; } private static List createSortedAdditionalPathEntries(List additionalPathEntries) { final List result = new ArrayList<>(additionalPathEntries); final Map matchesClassNPatternMemo = new HashMap<>(); for (File file : result) { final String name = file.getName(); matchesClassNPatternMemo.put(name, ShareConstants.CLASS_N_PATTERN.matcher(name).matches()); } Collections.sort(result, new Comparator() { @Override public int compare(File lhs, File rhs) { if (lhs == null && rhs == null) { return 0; } if (lhs == null) { return -1; } if (rhs == null) { return 1; } final String lhsName = lhs.getName(); final String rhsName = rhs.getName(); if (lhsName.equals(rhsName)) { return 0; } final String testDexSuffix = ShareConstants.TEST_DEX_NAME; // test.dex should always be at tail. if (lhsName.startsWith(testDexSuffix)) { return 1; } if (rhsName.startsWith(testDexSuffix)) { return -1; } final boolean isLhsNameMatchClassN = matchesClassNPatternMemo.get(lhsName); final boolean isRhsNameMatchClassN = matchesClassNPatternMemo.get(rhsName); if (isLhsNameMatchClassN && isRhsNameMatchClassN) { final int lhsDotPos = lhsName.indexOf('.'); final int rhsDotPos = rhsName.indexOf('.'); final int lhsId = (lhsDotPos > 7 ? Integer.parseInt(lhsName.substring(7, lhsDotPos)) : 1); final int rhsId = (rhsDotPos > 7 ? Integer.parseInt(rhsName.substring(7, rhsDotPos)) : 1); return (lhsId == rhsId ? 0 : (lhsId < rhsId ? -1 : 1)); } else if (isLhsNameMatchClassN) { // Dex name that matches class N rules should always be at first. return -1; } else if (isRhsNameMatchClassN) { return 1; } return lhsName.compareTo(rhsName); } }); return result; } /** * Installer for platform huawei ark */ private static final class ArkHot { private static void install(ClassLoader loader, List additionalClassPathEntries) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException, ClassNotFoundException, SecurityException { Class extendedClassLoaderHelper = ClassLoader.getSystemClassLoader() .getParent().loadClass("com.huawei.ark.classloader.ExtendedClassLoaderHelper"); for (File file : additionalClassPathEntries) { String path = file.getCanonicalPath(); Method applyPatchMethod = extendedClassLoaderHelper.getDeclaredMethod( "applyPatch", ClassLoader.class, String.class); applyPatchMethod.setAccessible(true); applyPatchMethod.invoke(null, loader, path); ShareTinkerLog.i(TAG, "ArkHot install path = " + path); } } } /** * Installer for platform versions 23. */ private static final class V23 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { /* The patched class loader is expected to be a descendant of * dalvik.system.BaseDexClassLoader. We modify its * dalvik.system.DexPathList pathList field to append additional DEX * file entries. */ Field pathListField = ShareReflectUtil.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList suppressedExceptions = new ArrayList(); ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); if (suppressedExceptions.size() > 0) { for (IOException e : suppressedExceptions) { ShareTinkerLog.w(TAG, "Exception in makePathElement", e); throw e; } } } /** * A wrapper around * {@code private static final dalvik.system.DexPathList#makePathElements}. */ private static Object[] makePathElements( Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makePathElements; try { makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class, List.class); } catch (NoSuchMethodException e) { ShareTinkerLog.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure"); try { makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class); } catch (NoSuchMethodException e1) { ShareTinkerLog.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure"); try { ShareTinkerLog.e(TAG, "NoSuchMethodException: try use v19 instead"); return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions); } catch (NoSuchMethodException e2) { ShareTinkerLog.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure"); throw e2; } } } return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); } } /** * Installer for platform versions 19. */ private static final class V19 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException { /* The patched class loader is expected to be a descendant of * dalvik.system.BaseDexClassLoader. We modify its * dalvik.system.DexPathList pathList field to append additional DEX * file entries. */ Field pathListField = ShareReflectUtil.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ArrayList suppressedExceptions = new ArrayList(); ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); if (suppressedExceptions.size() > 0) { for (IOException e : suppressedExceptions) { ShareTinkerLog.w(TAG, "Exception in makeDexElement", e); throw e; } } } /** * A wrapper around * {@code private static final dalvik.system.DexPathList#makeDexElements}. */ private static Object[] makeDexElements( Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = null; try { makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class); } catch (NoSuchMethodException e) { ShareTinkerLog.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure"); try { makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", List.class, File.class, List.class); } catch (NoSuchMethodException e1) { ShareTinkerLog.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure"); throw e1; } } return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); } } /** * Installer for platform versions 14, 15, 16, 17 and 18. */ private static final class V14 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException { /* The patched class loader is expected to be a descendant of * dalvik.system.BaseDexClassLoader. We modify its * dalvik.system.DexPathList pathList field to append additional DEX * file entries. */ Field pathListField = ShareReflectUtil.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory)); } /** * A wrapper around * {@code private static final dalvik.system.DexPathList#makeDexElements}. */ private static Object[] makeDexElements( Object dexPathList, ArrayList files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); } } /** * Installer for platform versions 4 to 13. */ private static final class V4 { private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, IOException { /* The patched class loader is expected to be a descendant of * dalvik.system.DexClassLoader. We modify its * fields mPaths, mFiles, mZips and mDexs to append additional DEX * file entries. */ int extraSize = additionalClassPathEntries.size(); Field pathField = ShareReflectUtil.findField(loader, "path"); StringBuilder path = new StringBuilder((String) pathField.get(loader)); String[] extraPaths = new String[extraSize]; File[] extraFiles = new File[extraSize]; ZipFile[] extraZips = new ZipFile[extraSize]; DexFile[] extraDexs = new DexFile[extraSize]; for (ListIterator iterator = additionalClassPathEntries.listIterator(); iterator.hasNext();) { File additionalEntry = iterator.next(); String entryPath = additionalEntry.getAbsolutePath(); path.append(':').append(entryPath); int index = iterator.previousIndex(); extraPaths[index] = entryPath; extraFiles[index] = additionalEntry; extraZips[index] = new ZipFile(additionalEntry); //edit by zhangshaowen String outputPathName = SharePatchFileUtil.optimizedPathFor(additionalEntry, optimizedDirectory); //for below 4.0, we must input jar or zip extraDexs[index] = DexFile.loadDex(entryPath, outputPathName, 0); } pathField.set(loader, path.toString()); ShareReflectUtil.expandFieldArray(loader, "mPaths", extraPaths); ShareReflectUtil.expandFieldArray(loader, "mFiles", extraFiles); ShareReflectUtil.expandFieldArray(loader, "mZips", extraZips); try { ShareReflectUtil.expandFieldArray(loader, "mDexs", extraDexs); } catch (Exception e) { // Ignored. } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerArkHotLoader.java ================================================ /* * Copyright (C) 2019-2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.loader; import android.annotation.TargetApi; import android.content.Intent; import android.os.Build; import android.util.Log; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareArkHotDiffPatchInfo; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import dalvik.system.PathClassLoader; /** * Created by on 16/3/8. * check the complete of the dex files * pre-load patch dex files */ public class TinkerArkHotLoader { private static final String TAG = "Tinker.TinkerArkHotLoader"; private static final String ARK_MEAT_FILE = ShareConstants.ARKHOT_META_FILE; private static final String ARKHOT_PATH = ShareConstants.ARKHOTFIX_PATH; // private static File testOptDexFile; private static HashSet arkHotApkInfo = new HashSet<>(); private static boolean isArkHotRuning = ShareTinkerInternals.isArkHotRuning(); private TinkerArkHotLoader() { } /** * Load tinker JARs and add them to * the Application ClassLoader. * * @param application The application. */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public static boolean loadTinkerArkHot(final TinkerApplication application, String directory, Intent intentResult) { if (arkHotApkInfo.isEmpty()) { ShareTinkerLog.w(TAG, "there is no apk to load"); return true; } PathClassLoader classLoader = (PathClassLoader) TinkerArkHotLoader.class.getClassLoader(); if (classLoader != null) { ShareTinkerLog.i(TAG, "classloader: " + classLoader.toString()); } else { ShareTinkerLog.e(TAG, "classloader is null"); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL); return false; } String apkPath = directory + "/" + ARKHOT_PATH + "/"; ArrayList legalFiles = new ArrayList<>(); // verify merge classN.apk if (isArkHotRuning && !arkHotApkInfo.isEmpty()) { File classNFile = null; classNFile = new File(apkPath + ShareConstants.ARKHOT_PATCH_NAME); legalFiles.add(classNFile); } try { // 加载Apk SystemClassLoaderAdder.installApk(classLoader, legalFiles); } catch (Throwable e) { ShareTinkerLog.e(TAG, "install dexes failed"); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION); return false; } return true; } /** * all the ark apk in meta file exist? * fast check, only check whether exist * * @return boolean */ public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, Intent intentResult) { String meta = securityCheck.getMetaContentMap().get(ARK_MEAT_FILE); if (meta == null) { return true; } arkHotApkInfo.clear(); ArrayList allDexInfo = new ArrayList<>(); ShareArkHotDiffPatchInfo.parseDiffPatchInfo(meta, allDexInfo); if (allDexInfo.isEmpty()) { return true; } HashMap apks = new HashMap<>(1); for (ShareArkHotDiffPatchInfo info : allDexInfo) { // for dalvik, ignore art support dex if (!ShareArkHotDiffPatchInfo.checkDiffPatchInfo(info)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } if (isArkHotRuning && ShareConstants.ARKHOT_PATCH_NAME.equals(info.name)) { arkHotApkInfo.add(info); } } if (isArkHotRuning && !arkHotApkInfo.isEmpty()) { apks.put(ShareConstants.ARKHOT_PATCH_NAME, ""); } String apkDirectory = directory + "/" + ARKHOT_PATH + "/"; File dexDir = new File(apkDirectory); if (!dexDir.exists() || !dexDir.isDirectory()) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST); return false; } // fast check whether there is any dex files missing for (String name : apks.keySet()) { File apkFile = new File(apkDirectory + name); if (!SharePatchFileUtil.isLegalFile(apkFile)) { try { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, apkFile.getCanonicalPath()); } catch (IOException e) { e.printStackTrace(); } ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST); return false; } } // if is ok, add to result intent intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, apks); return true; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerClassLoader.java ================================================ package com.tencent.tinker.loader; import android.annotation.SuppressLint; import com.tencent.tinker.anno.Keep; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.NoSuchElementException; import dalvik.system.BaseDexClassLoader; import dalvik.system.PathClassLoader; /** * Created by tangyinsheng on 2020-01-09. */ @Keep @SuppressLint("NewApi") public final class TinkerClassLoader extends PathClassLoader { private final ClassLoader mOriginAppClassLoader; TinkerClassLoader(String dexPath, File optimizedDir, String libraryPath, ClassLoader originAppClassLoader) { super("", libraryPath, ClassLoader.getSystemClassLoader()); mOriginAppClassLoader = originAppClassLoader; injectDexPath(this, dexPath, optimizedDir); } @Override protected Class findClass(String name) throws ClassNotFoundException { Class cl = null; try { cl = super.findClass(name); } catch (ClassNotFoundException ignored) { cl = null; } if (cl != null) { return cl; } else { return mOriginAppClassLoader.loadClass(name); } } @Override public URL getResource(String name) { // The lookup order we use here is the same as for classes. URL resource = Object.class.getClassLoader().getResource(name); if (resource != null) { return resource; } resource = findResource(name); if (resource != null) { return resource; } return mOriginAppClassLoader.getResource(name); } @Override public Enumeration getResources(String name) throws IOException { @SuppressWarnings("unchecked") final Enumeration[] resources = (Enumeration[]) new Enumeration[] { Object.class.getClassLoader().getResources(name), findResources(name), mOriginAppClassLoader.getResources(name) }; return new CompoundEnumeration<>(resources); } private static void injectDexPath(ClassLoader cl, String dexPath, File optimizedDir) { try { final List dexFiles = new ArrayList<>(16); for (String oneDexPath : dexPath.split(":")) { if (oneDexPath.isEmpty()) { continue; } dexFiles.add(new File(oneDexPath)); } if (!dexFiles.isEmpty()) { SystemClassLoaderAdder.injectDexesInternal(cl, dexFiles, optimizedDir); } } catch (Throwable thr) { throw new TinkerRuntimeException("Fail to create TinkerClassLoader.", thr); } } @Keep class CompoundEnumeration implements Enumeration { private Enumeration[] enums; private int index = 0; public CompoundEnumeration(Enumeration[] enums) { this.enums = enums; } @Override public boolean hasMoreElements() { while (index < enums.length) { if (enums[index] != null && enums[index].hasMoreElements()) { return true; } index++; } return false; } @Override public E nextElement() { if (!hasMoreElements()) { throw new NoSuchElementException(); } return enums[index].nextElement(); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Intent; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareDexDiffPatchInfo; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; /** * Created by zhangshaowen on 16/3/8. * check the complete of the dex files * pre-load patch dex files */ public class TinkerDexLoader { private static final String TAG = "Tinker.TinkerDexLoader"; private static final String DEX_MEAT_FILE = ShareConstants.DEX_META_FILE; private static final String DEX_PATH = ShareConstants.DEX_PATH; private static final String DEFAULT_DEX_OPTIMIZE_PATH = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; private static final String INTERPRET_DEX_OPTIMIZE_PATH = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH; private static final ArrayList LOAD_DEX_LIST = new ArrayList<>(); // private static File testOptDexFile; private static HashSet classNDexInfo = new HashSet<>(); private static boolean isVmArt = ShareTinkerInternals.isVmArt(); private TinkerDexLoader() { } /** * Load tinker JARs and add them to * the Application ClassLoader. * * @param application The application. */ public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA, boolean isProtectedApp) { if (LOAD_DEX_LIST.isEmpty() && classNDexInfo.isEmpty()) { ShareTinkerLog.w(TAG, "there is no dex to load"); return true; } ClassLoader classLoader = TinkerDexLoader.class.getClassLoader(); if (classLoader != null) { ShareTinkerLog.i(TAG, "classloader: " + classLoader.toString()); } else { ShareTinkerLog.e(TAG, "classloader is null"); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL); return false; } String dexPath = directory + "/" + DEX_PATH + "/"; ArrayList legalFiles = new ArrayList<>(); for (ShareDexDiffPatchInfo info : LOAD_DEX_LIST) { //for dalvik, ignore art support dex if (isJustArtSupportDex(info)) { continue; } String path = dexPath + info.realName; File file = new File(path); if (application.isTinkerLoadVerifyFlag()) { long start = System.currentTimeMillis(); String checkMd5 = getInfoMd5(info); if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) { //it is good to delete the mismatch file ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH, file.getAbsolutePath()); return false; } ShareTinkerLog.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start)); } legalFiles.add(file); } // verify merge classN.apk if (isVmArt && !classNDexInfo.isEmpty()) { File classNFile = new File(dexPath + ShareConstants.CLASS_N_APK_NAME); long start = System.currentTimeMillis(); if (application.isTinkerLoadVerifyFlag()) { for (ShareDexDiffPatchInfo info : classNDexInfo) { if (!SharePatchFileUtil.verifyDexFileMd5(classNFile, info.rawName, info.destMd5InArt)) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH, classNFile.getAbsolutePath()); return false; } } ShareTinkerLog.i(TAG, "verify dex file:" + classNFile.getPath() + " md5, use time: " + (System.currentTimeMillis() - start)); } legalFiles.add(classNFile); } File optimizeDir = new File(directory + "/" + oatDir); if (isSystemOTA) { final boolean[] parallelOTAResult = {true}; final Throwable[] parallelOTAThrowable = new Throwable[1]; String targetISA; try { targetISA = ShareTinkerInternals.getCurrentInstructionSet(); } catch (Throwable throwable) { ShareTinkerLog.i(TAG, "getCurrentInstructionSet fail:" + throwable); // try { // targetISA = ShareOatUtil.getOatFileInstructionSet(testOptDexFile); // } catch (Throwable throwable) { // don't ota on the front deleteOutOfDateOATFile(directory); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, throwable); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION); return false; // } } deleteOutOfDateOATFile(directory); ShareTinkerLog.w(TAG, "systemOTA, try parallel oat dexes, targetISA:" + targetISA); // change dir optimizeDir = new File(directory + "/" + INTERPRET_DEX_OPTIMIZE_PATH); TinkerDexOptimizer.optimizeAll( application, legalFiles, optimizeDir, true, application.isUseDelegateLastClassLoader(), targetISA, false, new TinkerDexOptimizer.ResultCallback() { long start; @Override public void onStart(File dexFile, File optimizedDir) { start = System.currentTimeMillis(); ShareTinkerLog.i(TAG, "start to optimize dex:" + dexFile.getPath()); } @Override public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) { // Do nothing. ShareTinkerLog.i(TAG, "success to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start)); } @Override public void onFailed(File dexFile, File optimizedDir, Throwable thr) { parallelOTAResult[0] = false; parallelOTAThrowable[0] = thr; ShareTinkerLog.i(TAG, "fail to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start)); } } ); if (!parallelOTAResult[0]) { ShareTinkerLog.e(TAG, "parallel oat dexes failed"); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, parallelOTAThrowable[0]); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION); return false; } } try { final boolean useDLC = application.isUseDelegateLastClassLoader(); SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles, isProtectedApp, useDLC); } catch (Throwable e) { ShareTinkerLog.e(TAG, "install dexes failed"); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION); return false; } return true; } /** * all the dex files in meta file exist? * fast check, only check whether exist * * @return boolean */ public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) { String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE); //not found dex if (meta == null) { return true; } LOAD_DEX_LIST.clear(); classNDexInfo.clear(); ArrayList allDexInfo = new ArrayList<>(); ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo); if (allDexInfo.isEmpty()) { return true; } HashMap dexes = new HashMap<>(); ShareDexDiffPatchInfo testInfo = null; for (ShareDexDiffPatchInfo info : allDexInfo) { //for dalvik, ignore art support dex if (isJustArtSupportDex(info)) { continue; } if (!ShareDexDiffPatchInfo.checkDexDiffPatchInfo(info)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } if (isVmArt && info.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) { testInfo = info; } else if (isVmArt && ShareConstants.CLASS_N_PATTERN.matcher(info.realName).matches()) { classNDexInfo.add(info); } else { dexes.put(info.realName, getInfoMd5(info)); LOAD_DEX_LIST.add(info); } } if (isVmArt && (testInfo != null || !classNDexInfo.isEmpty())) { if (testInfo != null) { classNDexInfo.add(ShareTinkerInternals.changeTestDexToClassN(testInfo, classNDexInfo.size() + 1)); } dexes.put(ShareConstants.CLASS_N_APK_NAME, ""); } //tinker/patch.info/patch-641e634c/dex String dexDirectory = directory + "/" + DEX_PATH + "/"; File dexDir = new File(dexDirectory); if (!dexDir.exists() || !dexDir.isDirectory()) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST); return false; } String optimizeDexDirectory = directory + "/" + oatDir + "/"; File optimizeDexDirectoryFile = new File(optimizeDexDirectory); //fast check whether there is any dex files missing for (String name : dexes.keySet()) { File dexFile = new File(dexDirectory + name); if (!SharePatchFileUtil.isLegalFile(dexFile)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexFile.getAbsolutePath()); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST); return false; } //check dex opt whether complete also File dexOptFile = new File(SharePatchFileUtil.optimizedPathFor(dexFile, optimizeDexDirectoryFile)); if (!SharePatchFileUtil.isLegalFile(dexOptFile)) { if (SharePatchFileUtil.shouldAcceptEvenIfIllegal(dexOptFile)) { continue; } intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexOptFile.getAbsolutePath()); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST); return false; } // // find test dex // if (dexOptFile.getName().startsWith(ShareConstants.TEST_DEX_NAME)) { // testOptDexFile = dexOptFile; // } } //if is ok, add to result intent intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, dexes); return true; } private static String getInfoMd5(ShareDexDiffPatchInfo info) { return isVmArt ? info.destMd5InArt : info.destMd5InDvm; } private static void deleteOutOfDateOATFile(String directory) { String optimizeDexDirectory = directory + "/" + DEFAULT_DEX_OPTIMIZE_PATH + "/"; SharePatchFileUtil.deleteDir(optimizeDexDirectory); // delete android o if (ShareTinkerInternals.isAfterAndroidO()) { String androidODexDirectory = directory + "/" + DEX_PATH + "/" + ShareConstants.ANDROID_O_DEX_OPTIMIZE_PATH + "/"; SharePatchFileUtil.deleteDir(androidODexDirectory); } } private static boolean isJustArtSupportDex(ShareDexDiffPatchInfo dexDiffPatchInfo) { if (isVmArt) { return false; } String destMd5InDvm = dexDiffPatchInfo.destMd5InDvm; if (destMd5InDvm.equals("0")) { return true; } return false; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import static com.tencent.tinker.loader.shareutil.ShareConstants.ODEX_SUFFIX; import static com.tencent.tinker.loader.shareutil.ShareConstants.VDEX_SUFFIX; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareFileLockHelper; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import dalvik.system.DexFile; /** * Created by tangyinsheng on 2016/11/15. */ public final class TinkerDexOptimizer { private static final String TAG = "Tinker.ParallelDex"; private static final String INTERPRET_LOCK_FILE_NAME = "interpret.lock"; /** * Optimize (trigger dexopt or dex2oat) dexes. * * @param dexFiles * @param optimizedDir * @param cb * @return If all dexes are optimized successfully, return true. Otherwise return false. */ public static boolean optimizeAll(Context context, Collection dexFiles, File optimizedDir, boolean useDLC, boolean useEmergencyMode, ResultCallback cb) { final String targetISA = ShareTinkerInternals.getCurrentInstructionSet(); return optimizeAll(context, dexFiles, optimizedDir, false, useDLC, targetISA, useEmergencyMode, cb); } public static boolean optimizeAll(Context context, Collection dexFiles, File optimizedDir, boolean useInterpretMode, boolean useDLC, String targetISA, boolean useEmergencyMode, ResultCallback cb) { ArrayList sortList = new ArrayList<>(dexFiles); // sort input dexFiles with its file length in reverse order. Collections.sort(sortList, new Comparator() { @Override public int compare(File lhs, File rhs) { final long lhsSize = lhs.length(); final long rhsSize = rhs.length(); if (lhsSize < rhsSize) { return 1; } else if (lhsSize == rhsSize) { return 0; } else { return -1; } } }); for (File dexFile : sortList) { OptimizeWorker worker = new OptimizeWorker(context, dexFile, optimizedDir, useInterpretMode, useDLC, targetISA, useEmergencyMode, cb); if (!worker.run()) { return false; } } return true; } public interface ResultCallback { void onStart(File dexFile, File optimizedDir); void onSuccess(File dexFile, File optimizedDir, File optimizedFile); void onFailed(File dexFile, File optimizedDir, Throwable thr); } private static class OptimizeWorker { private static ClassLoader patchClassLoaderStrongRef = null; private final String targetISA; private final Context context; private final File dexFile; private final File optimizedDir; private final boolean useInterpretMode; private final boolean useDLC; private final boolean useEmergencyMode; private final ResultCallback callback; OptimizeWorker(Context context, File dexFile, File optimizedDir, boolean useInterpretMode, boolean useDLC, String targetISA, boolean useEmergencyMode, ResultCallback cb) { this.context = context; this.dexFile = dexFile; this.optimizedDir = optimizedDir; this.useInterpretMode = useInterpretMode; this.useDLC = useDLC; this.callback = cb; this.targetISA = targetISA; this.useEmergencyMode = useEmergencyMode; } boolean run() { try { if (!SharePatchFileUtil.isLegalFile(dexFile)) { if (callback != null) { callback.onFailed(dexFile, optimizedDir, new IOException("dex file " + dexFile.getAbsolutePath() + " is not exist!")); return false; } } if (callback != null) { callback.onStart(dexFile, optimizedDir); } String optimizedPath = SharePatchFileUtil.optimizedPathFor(this.dexFile, this.optimizedDir); if (!ShareTinkerInternals.isArkHotRuning()) { if (useInterpretMode) { interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath, targetISA); } else if (TinkerApplication.getInstance().isUseInterpretModeOnSupported32BitSystem() && ShareTinkerInternals.isVersionInRange(21, 25, true) && ShareTinkerInternals.is32BitEnv() ) { try { ShareTinkerLog.i(TAG, "dexopt with interpret mode on 32bit supported system was enabled."); interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath, targetISA); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "exception occurred on dexopt triggering."); } if (!SharePatchFileUtil.isLegalFile(new File(optimizedPath))) { ShareTinkerLog.w(TAG, "interpret dexopt failure, compensate with system method."); DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0); } } else if (ShareTinkerInternals.isNewerOrEqualThanVersion(26, true)) { if (ShareTinkerInternals.isNewerOrEqualThanVersion(29, true)) { createFakeODexPathStructureOnDemand(optimizedPath); patchClassLoaderStrongRef = NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir, useDLC, dexFile.getAbsolutePath()); final Runnable task = new Runnable() { @Override public void run() { try { triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to call triggerPMDexOptAsyncOnDemand."); } finally { if (!useEmergencyMode) { final String vdexPath = optimizedPath.substring(0, optimizedPath.lastIndexOf(ODEX_SUFFIX)) + VDEX_SUFFIX; waitUntilFileGeneratedOrTimeout(context, vdexPath); } } } }; if (useEmergencyMode) { new Thread(task, "TinkerDex2oatTrigger").start(); } else { task.run(); } } else { patchClassLoaderStrongRef = NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir, useDLC, dexFile.getAbsolutePath()); } } else { DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0); } } final File odexFile = new File(optimizedPath); if (SharePatchFileUtil.isLegalFile(odexFile) || SharePatchFileUtil.shouldAcceptEvenIfIllegal(odexFile)) { if (callback != null) { callback.onSuccess(dexFile, optimizedDir, odexFile); } return true; } else { final FileNotFoundException e = new FileNotFoundException("Odex file: " + odexFile.getAbsolutePath() + " does not exist."); if (callback != null) { callback.onFailed(dexFile, optimizedDir, e); } return false; } } catch (final Throwable e) { ShareTinkerLog.e(TAG, "Failed to optimize dex: " + dexFile.getAbsolutePath(), e); if (callback != null) { callback.onFailed(dexFile, optimizedDir, e); } return false; } } } private static void createFakeODexPathStructureOnDemand(String odexPath) { if (!ShareTinkerInternals.isNewerOrEqualThanVersion(29, true)) { return; } ShareTinkerLog.i(TAG, "Creating fake odex path structure."); final File odexFile = new File(odexPath); if (!odexFile.exists()) { final File odexDir = odexFile.getParentFile(); if (!odexDir.exists()) { odexDir.mkdirs(); } try { odexFile.createNewFile(); } catch (Throwable ignored) { // Ignored. } } } private static void triggerPMDexOptOnDemand(Context context, String dexPath, String oatPath) throws Exception { if (!ShareTinkerInternals.isNewerOrEqualThanVersion(29, true)) { // Only do this trick on Android Q, R and newer devices. ShareTinkerLog.w(TAG, "[+] Not API 29, 30 and newer device, skip triggering dexopt."); return; } // Android Q is significantly slowed down by Fallback Dex Loading procedure, so we // trigger background dexopt to generate executable odex here. ShareTinkerLog.i(TAG, "[+] Hit target device, do dexopt logic now."); final File oatFile = new File(oatPath); if (SharePatchFileUtil.isLegalFile(oatFile)) { ShareTinkerLog.i(TAG, "[+] Oat file %s should be valid, skip triggering dexopt.", oatPath); return; } final File dexFile = new File(dexPath); for (int i = 0; i < 10; ++i) { if (triggerSecondaryDexOpt(context, dexFile, oatFile, true)) { return; } } if (!SharePatchFileUtil.isLegalFile(oatFile)) { if ("huawei".equalsIgnoreCase(Build.MANUFACTURER) || "honor".equalsIgnoreCase(Build.MANUFACTURER)) { for (int i = 0; i < 5; ++i) { try { registerDexModule(context, dexPath); if (SharePatchFileUtil.isLegalFile(oatFile)) { break; } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "[-] Error."); } SystemClock.sleep(3000); } if (!SharePatchFileUtil.isLegalFile(oatFile)) { throw new IllegalStateException("No odex file was generated after calling registerDexModule"); } } else { throw new IllegalStateException("No odex file was generated after calling performDexOptSecondary"); } } } private static boolean triggerSecondaryDexOpt(Context context, File dexFile, File oatFile, boolean waitForOAT) { try { performDexOptSecondary(context); if (SharePatchFileUtil.isLegalFile(oatFile)) { return true; } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "[-] Error."); } try { performBgDexOptJob(context); if (SharePatchFileUtil.isLegalFile(oatFile)) { return true; } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "[-] Error."); } try { performDexOptSecondaryByTransactionCode(context); if (SharePatchFileUtil.isLegalFile(oatFile)) { return true; } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "[-] Error."); } if (waitForOAT) { return waitUntilFileGeneratedOrTimeout(context, oatFile.getAbsolutePath(), 3000L); } else { return SharePatchFileUtil.isLegalFile(oatFile); } } private static void performDexOptSecondary(Context context) throws IllegalStateException { /* * Use 'speed-profile' as compile filter can take advantage bring by profiling jit and '.art' cache. * Meanwhile dex2oat can still be done in almost the same time as using 'quicken'. * Thanks to Chen MinSheng for his advice. */ final String[] args = { "compile", "-f", "--secondary-dex", "-m", ShareTinkerInternals.isNewerOrEqualThanVersion(31 /* Android S */, true) ? "verify" : "speed-profile", context.getPackageName() }; executePMSShellCommand(context, args); } private static void performBgDexOptJob(Context context) throws IllegalStateException { final String[] args = { "bg-dexopt-job", context.getPackageName() }; executePMSShellCommand(context, args); } private static final int[] sPerformDexOptSecondaryTransactionCode = {-1}; private static void performDexOptSecondaryByTransactionCode(Context context) throws IllegalStateException { synchronized (sPerformDexOptSecondaryTransactionCode) { if (sPerformDexOptSecondaryTransactionCode[0] == -1) { try { final Method getDeclaredFieldMethod = ShareReflectUtil.findMethod( Class.class, "getDeclaredField", String.class); getDeclaredFieldMethod.setAccessible(true); final Field cstField = (Field) getDeclaredFieldMethod.invoke( Class.forName("android.content.pm.IPackageManager$Stub"), "TRANSACTION_performDexOptSecondary" ); cstField.setAccessible(true); sPerformDexOptSecondaryTransactionCode[0] = (int) cstField.get(null); } catch (Throwable thr) { throw new IllegalStateException("Cannot query transaction code of performDexOptSecondary.", thr); } } } ShareTinkerLog.i(TAG, "[+] performDexOptSecondaryByTransactionCode, code: %s", sPerformDexOptSecondaryTransactionCode[0]); final IBinder pmsBinder = getPMSBinderProxy(context); Parcel data = null; Parcel reply = null; try { data = Parcel.obtain(); reply = Parcel.obtain(); boolean result; try { data.writeInterfaceToken(pmsBinder.getInterfaceDescriptor()); data.writeString(context.getPackageName()); final String compileFilter = ShareTinkerInternals.isNewerOrEqualThanVersion(31 /* Android S */, true) ? "verify" : "speed-profile"; data.writeString(compileFilter); data.writeInt(1); // force boolean status = pmsBinder.transact(sPerformDexOptSecondaryTransactionCode[0], data, reply, 0); if (!status) { throw new IllegalStateException("Binder transaction failure."); } } catch (RemoteException e) { throw new IllegalStateException(e); } try { reply.readException(); } catch (Throwable thr) { throw new IllegalStateException(thr); } result = (0 != reply.readInt()); if (!result) { ShareTinkerLog.w(TAG, "[!] System API return false."); } } finally { if (reply != null) { reply.recycle(); } if (data != null) { data.recycle(); } } } private static final IBinder[] sPMSBinderProxy = { null }; private static IBinder getPMSBinderProxy(Context context) throws IllegalStateException { synchronized (sPMSBinderProxy) { final IBinder cachedPMSBinderProxy = sPMSBinderProxy[0]; if (cachedPMSBinderProxy != null && cachedPMSBinderProxy.isBinderAlive()) { return cachedPMSBinderProxy; } try { final Class smClazz = Class.forName("android.os.ServiceManager"); final Method getServiceMethod = ShareReflectUtil.findMethod(smClazz, "getService", String.class); sPMSBinderProxy[0] = (IBinder) getServiceMethod.invoke(null, "package"); return sPMSBinderProxy[0]; } catch (Throwable thr) { if (thr instanceof InvocationTargetException) { throw new IllegalStateException(((InvocationTargetException) thr).getTargetException()); } else { throw new IllegalStateException(thr); } } } } private static final int SHELL_COMMAND_TRANSACTION = ('_' << 24) | ('C' << 16) | ('M' << 8) | 'D'; private static final Handler sHandler = new Handler(Looper.getMainLooper()); private static final ResultReceiver sEmptyResultReceiver = new ResultReceiver(sHandler); /** * Clever way to avoid hacking an unstable binder transaction code of PMS. * Credit: https://mp.weixin.qq.com/s/5kwU-84TbsO3Tk5QDzNKwA */ private static void executePMSShellCommand(Context context, String[] args) throws IllegalStateException { final IBinder pmsBinderProxy = getPMSBinderProxy(context); Parcel data = null; Parcel reply = null; long lastIdentity = Binder.clearCallingIdentity(); try { ShareTinkerLog.i(TAG, "[+] Execute shell cmd, args: %s", Arrays.toString(args)); data = Parcel.obtain(); reply = Parcel.obtain(); data.writeFileDescriptor(FileDescriptor.in); data.writeFileDescriptor(FileDescriptor.out); data.writeFileDescriptor(FileDescriptor.err); data.writeStringArray(args); data.writeStrongBinder(null /* ShellCallback */); sEmptyResultReceiver.writeToParcel(data, 0); pmsBinderProxy.transact(SHELL_COMMAND_TRANSACTION, data, reply, 0); reply.readException(); ShareTinkerLog.i(TAG, "[+] Execute shell cmd done."); } catch (Throwable thr) { throw new IllegalStateException("Failure on executing shell cmd.", thr); } finally { if (reply != null) { reply.recycle(); } if (data != null) { data.recycle(); } Binder.restoreCallingIdentity(lastIdentity); } } private static void registerDexModule(Context context, String dexPath) throws IllegalStateException { final PackageManager synchronizedPM = getSynchronizedPackageManager(context); try { final Class dexModuleRegisterCallbackClazz = Class .forName("android.content.pm.PackageManager$DexModuleRegisterCallback"); ShareReflectUtil .findMethod(synchronizedPM, "registerDexModule", String.class, dexModuleRegisterCallbackClazz) .invoke(synchronizedPM, dexPath, null); } catch (InvocationTargetException e) { throw new IllegalStateException(e.getTargetException()); } catch (Throwable thr) { if (thr instanceof IllegalStateException) { throw (IllegalStateException) thr; } else { throw new IllegalStateException(thr); } } } private static final PackageManager[] sSynchronizedPMCache = { null }; private static final PackageManager getSynchronizedPackageManager(Context context) throws IllegalStateException { synchronized (sSynchronizedPMCache) { try { if (sSynchronizedPMCache[0] != null) { synchronized (sPMSBinderProxy) { if (sPMSBinderProxy[0] != null && sPMSBinderProxy[0].isBinderAlive()) { return sSynchronizedPMCache[0]; } } } final IBinder pmsBinderProxy = getPMSBinderProxy(context); final IBinder syncPMSBinderProxy = (IBinder) Proxy.newProxyInstance( context.getClassLoader(), pmsBinderProxy.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("transact".equals(method.getName())) { // FLAG_ONEWAY => NONE. args[3] = 0; } return method.invoke(pmsBinderProxy, args); } } ); final Class pmsStubClazz = Class.forName("android.content.pm.IPackageManager$Stub"); final Object pmsStubProxy = ShareReflectUtil .findMethod(pmsStubClazz, "asInterface", IBinder.class) .invoke(null, syncPMSBinderProxy); final Class appPMClazz = Class.forName("android.app.ApplicationPackageManager"); final Object contextImpl = (context instanceof ContextWrapper) ? ((ContextWrapper) context).getBaseContext() : context; final Class pmsItfClazz = Class.forName("android.content.pm.IPackageManager"); final PackageManager appPM = (PackageManager) ShareReflectUtil .findConstructor(appPMClazz, contextImpl.getClass(), pmsItfClazz) .newInstance(contextImpl, pmsStubProxy); sSynchronizedPMCache[0] = appPM; return appPM; } catch (InvocationTargetException e) { throw new IllegalStateException(e.getTargetException()); } catch (Throwable thr) { if (thr instanceof IllegalStateException) { throw (IllegalStateException) thr; } else { throw new IllegalStateException(thr); } } } } private static boolean waitUntilFileGeneratedOrTimeout(Context context, String filePath, Long... timeOutSeq) { final File file = new File(filePath); final Long[] delaySeq = (timeOutSeq != null && timeOutSeq.length > 0) ? timeOutSeq : new Long[] {1000L, 2000L, 4000L, 8000L, 16000L, 32000L}; int delaySeqIdx = 0; while (!SharePatchFileUtil.isLegalFile(file) && delaySeqIdx < delaySeq.length) { SystemClock.sleep(delaySeq[delaySeqIdx++]); ShareTinkerLog.w(TAG, "[!] File %s does not exist after waiting %s time(s), wait again.", filePath, delaySeqIdx); } if (SharePatchFileUtil.isLegalFile(file)) { ShareTinkerLog.i(TAG, "[+] File %s was found.", filePath); return true; } else { ShareTinkerLog.e(TAG, "[-] File %s does not exist after waiting for %s times.", filePath, delaySeq.length); return false; } } private static void interpretDex2Oat(String dexFilePath, String oatFilePath, String targetISA) throws Exception { // add process lock for interpret mode final File oatFile = new File(oatFilePath); if (!oatFile.exists()) { oatFile.getParentFile().mkdirs(); } File lockFile = new File(oatFile.getParentFile(), INTERPRET_LOCK_FILE_NAME); ShareFileLockHelper fileLock = null; try { fileLock = ShareFileLockHelper.getFileLock(lockFile); final List commandAndParams = new ArrayList<>(); commandAndParams.add("dex2oat"); // for 7.1.1, duplicate class fix if (Build.VERSION.SDK_INT >= 24) { commandAndParams.add("--runtime-arg"); commandAndParams.add("-classpath"); commandAndParams.add("--runtime-arg"); commandAndParams.add("&"); } commandAndParams.add("--dex-file=" + dexFilePath); commandAndParams.add("--oat-file=" + oatFilePath); commandAndParams.add("--instruction-set=" + targetISA); if (Build.VERSION.SDK_INT > 25) { commandAndParams.add("--compiler-filter=quicken"); } else { commandAndParams.add("--compiler-filter=interpret-only"); } final ProcessBuilder pb = new ProcessBuilder(commandAndParams); pb.redirectErrorStream(true); final Process dex2oatProcess = pb.start(); StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream()); StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream()); try { final int ret = dex2oatProcess.waitFor(); if (ret != 0) { throw new IOException("dex2oat works unsuccessfully, exit code: " + ret); } } catch (InterruptedException e) { throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e); } } finally { try { if (fileLock != null) { fileLock.close(); } } catch (IOException e) { ShareTinkerLog.w(TAG, "release interpret Lock error", e); } } } private static class StreamConsumer { static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor(); static void consumeInputStream(final InputStream is) { STREAM_CONSUMER.execute(new Runnable() { @Override public void run() { if (is == null) { return; } final byte[] buffer = new byte[256]; try { while ((is.read(buffer)) > 0) { // To satisfy checkstyle rules. } } catch (IOException ignored) { // Ignored. } finally { try { is.close(); } catch (Exception ignored) { // Ignored. } } } }); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Intent; import android.os.Build; import android.os.SystemClock; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.hotplug.ComponentHotplug; import com.tencent.tinker.loader.shareutil.Guard; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.SharePatchInfo; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by zhangshaowen on 16/3/10. * Warning, it is special for loader classes, they can't change through tinker patch. * thus, it's reference class must put in the tinkerPatch.dex.loader{} and the android main dex pattern through gradle */ public class TinkerLoader extends AbstractTinkerLoader { private static final String TAG = "Tinker.TinkerLoader"; // Hold current process guard lock and never release, until process dead. private static Guard sProcessGuardRef = null; /** * only main process can handle patch version change or incomplete */ @Override public Intent tryLoad(TinkerApplication app) { ShareTinkerLog.d(TAG, "tryLoad test test"); Intent resultIntent = new Intent(); Guard[] resultGuard = new Guard[1]; long begin = SystemClock.elapsedRealtime(); tryLoadPatchFilesInternal(app, resultIntent, resultGuard); long cost = SystemClock.elapsedRealtime() - begin; ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost); if (ShareIntentUtil.getIntentReturnCode(resultIntent) != ShareConstants.ERROR_LOAD_OK && resultGuard[0] != null) { // release guard if load failed resultGuard[0].close(); } sProcessGuardRef = resultGuard[0]; if (ShareTinkerInternals.isInMainProcess(app)) { tryCleanObsoletePatches(app); } return resultIntent; } private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent, Guard[] resultGuard) { final int tinkerFlag = app.getTinkerFlags(); if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles: tinker is disable, just return"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE); return; } if (ShareTinkerInternals.isInPatchProcess(app)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles: we don't load patch with :patch process itself, just return"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE); return; } //tinker File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app); if (patchDirectoryFile == null) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null"); //treat as not exist ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); return; } String patchDirectoryPath = patchDirectoryFile.getAbsolutePath(); //check patch directory whether exist if (!patchDirectoryFile.exists()) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patch dir not exist:" + patchDirectoryPath); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); return; } //tinker/patch.info File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath); //check patch info file whether exist if (!patchInfoFile.exists()) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath()); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST); return; } boolean mainProcess = ShareTinkerInternals.isInMainProcess(app); File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath); SharePatchInfo patchInfo = null; String oldVersion; String newVersion; String oatDex; String version; boolean versionChanged; String patchName; int retryCount = 0; do { //old = 641e634c5b8f1649c75caf73794acbdf //new = 2c150d8560334966952678930ba67fa8 final SharePatchInfo acquired = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); if (acquired == null) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); return; } oldVersion = acquired.oldVersion; newVersion = acquired.newVersion; oatDex = acquired.oatDir; if (oldVersion == null || newVersion == null || oatDex == null) { //it is nice to clean patch ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); return; } versionChanged = !(oldVersion.equals(newVersion)); oatDex = ShareTinkerInternals.getCurrentOatMode(app, oatDex); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, oatDex); version = oldVersion; if (versionChanged && mainProcess) { version = newVersion; } if (ShareTinkerInternals.isNullOrNil(version)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK); return; } //patch-641e634c patchName = SharePatchFileUtil.getPatchVersionDirectory(version); if (patchName == null) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patchName is null"); //we may delete patch info file ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST); return; } final File patchGuardDirectory = SharePatchFileUtil.getGuardDirectory(patchDirectoryPath); // tinker/guard/patch-641e634c final File patchGuardFile = new File(patchGuardDirectory, patchName); if (!patchGuardFile.exists()) { // Weird, the patch guard file is created before patch info file is updated. // Still check here. ShareTinkerLog.w(TAG, "tryLoadPatchFiles:patchGuardFile not exist"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_GUARD_FILE_NOT_EXIST); return; } final Guard guard = Guard.acquireUse(patchGuardFile); if (guard == null) { ShareTinkerLog.i(TAG, "tryLoadPatchFiles:cannot acquire guard, try again"); // A new patch may be applied, try get patch info again. retryCount++; continue; } patchInfo = acquired; resultGuard[0] = guard; break; } while (retryCount < 3); if (patchInfo == null) { // unreachable ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); return; } final boolean isProtectedApp = patchInfo.isProtectedApp; resultIntent.putExtra(ShareIntentUtil.INTENT_IS_PROTECTED_APP, isProtectedApp); final boolean useCustomPatch = patchInfo.useCustomPatch; resultIntent.putExtra(ShareIntentUtil.INTENT_USE_CUSTOM_PATCH, useCustomPatch); if (mainProcess) { if (patchInfo.isRemoveInterpretOATDir) { // delete interpret odex // for android o, directory change. Fortunately, we don't need to support android o interpret mode any more ShareTinkerLog.i(TAG, "tryLoadPatchFiles: isRemoveInterpretOATDir is true, try to delete interpret optimize files"); patchInfo.isRemoveInterpretOATDir = false; SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile); ShareTinkerInternals.killProcessExceptMain(app); final String patchNameToRemove = SharePatchFileUtil.getPatchVersionDirectory(newVersion); String patchVersionDirFullPath = patchDirectoryPath + "/" + patchNameToRemove; SharePatchFileUtil.deleteDirAsync(patchVersionDirFullPath + "/" + ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH); } } resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion); boolean oatModeChanged = oatDex.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH); if (ShareTinkerInternals.isNullOrNil(version)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK); return; } //tinker/patch.info/patch-641e634c String patchVersionDirectory = patchDirectoryPath + "/" + patchName; File patchVersionDirectoryFile = new File(patchVersionDirectory); if (!patchVersionDirectoryFile.exists()) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound"); //we may delete patch info file ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST); return; } //tinker/patch.info/patch-641e634c/patch-641e634c.apk final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version); File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null); if (!SharePatchFileUtil.isLegalFile(patchVersionFile)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound"); //we may delete patch info file ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST); return; } ShareSecurityCheck securityCheck = new ShareSecurityCheck(app); int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck); if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:checkTinkerPackage"); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return; } resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent()); final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag); final boolean isArkHotRuning = ShareTinkerInternals.isArkHotRuning(); if (!isArkHotRuning && isEnabledForDex) { //tinker/patch.info/patch-641e634c/dex boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent); if (!dexCheck) { //file not found, do not load patch ShareTinkerLog.w(TAG, "tryLoadPatchFiles:dex check fail"); return; } } final boolean isEnabledForArkHot = ShareTinkerInternals.isTinkerEnabledForArkHot(tinkerFlag); if (isArkHotRuning && isEnabledForArkHot) { boolean arkHotCheck = TinkerArkHotLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); if (!arkHotCheck) { // file not found, do not load patch ShareTinkerLog.w(TAG, "tryLoadPatchFiles:dex check fail"); return; } } final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag); if (isEnabledForNativeLib) { //tinker/patch.info/patch-641e634c/lib boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); if (!libCheck) { //file not found, do not load patch ShareTinkerLog.w(TAG, "tryLoadPatchFiles:native lib check fail"); return; } } //check resource final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag); ShareTinkerLog.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource); if (isEnabledForResource) { boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent); if (!resourceCheck) { //file not found, do not load patch ShareTinkerLog.w(TAG, "tryLoadPatchFiles:resource check fail"); return; } } //only work for art platform oat,because of interpret, refuse 4.4 art oat //android o use quicken default, we don't need to use interpret mode boolean isSystemOTA = ShareTinkerInternals.isVmArt() && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint) && Build.VERSION.SDK_INT >= 21 && !ShareTinkerInternals.isAfterAndroidO(); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, isSystemOTA); //we should first try rewrite patch info file, if there is a error, we can't load jar if (mainProcess) { if (versionChanged) { patchInfo.oldVersion = version; } if (oatModeChanged) { patchInfo.oatDir = oatDex; patchInfo.isRemoveInterpretOATDir = true; } } if (!checkSafeModeCount(app)) { if (mainProcess) { // Mark current patch as deleted so that other process will not load patch after reboot. patchInfo.oldVersion = ""; patchInfo.newVersion = ""; patchInfo.versionToRemove = ""; SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile); ShareTinkerInternals.killProcessExceptMain(app); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, new TinkerRuntimeException("checkSafeModeCount fail")); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION); ShareTinkerLog.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail, patch was deleted."); return; } else { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail, but we are not in main process, mark the patch to be deleted and continue load patch."); ShareTinkerInternals.cleanPatch(app); } } //now we can load patch resource if (isEnabledForResource) { boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent); if (!loadTinkerResources) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail"); return; } } //now we can load patch jar if (!isArkHotRuning && isEnabledForDex) { boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp); if (isSystemOTA) { // update fingerprint after load success patchInfo.fingerPrint = Build.FINGERPRINT; patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; // reset to false oatModeChanged = false; if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL); ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted"); return; } // update oat dir resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir); } if (!loadTinkerJars) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail"); return; } } if (isArkHotRuning && isEnabledForArkHot) { boolean loadArkHotFixJars = TinkerArkHotLoader.loadTinkerArkHot(app, patchVersionDirectory, resultIntent); if (!loadArkHotFixJars) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onPatchLoadArkApkFail"); return; } } // Init component hotplug support. if ((isEnabledForDex || isEnabledForArkHot) && isEnabledForResource) { ComponentHotplug.install(app, securityCheck); } if (!AppInfoChangedBlocker.tryStart(app)) { ShareTinkerLog.w(TAG, "tryLoadPatchFiles:AppInfoChangedBlocker install fail."); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_BAIL_HACK_FAILURE); return; } // Before successfully exit, we should update stored version info and kill other process // to make them load latest patch when we first applied newer one. if (mainProcess && (versionChanged || oatModeChanged)) { //update old version to new if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL); ShareTinkerLog.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted"); return; } ShareTinkerInternals.killProcessExceptMain(app); } //all is ok! ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK); ShareTinkerLog.i(TAG, "tryLoadPatchFiles: load end, ok!"); } private void tryCleanObsoletePatches(TinkerApplication app) { ShareTinkerLog.i(TAG, "try clean obsolete patches"); File patchDirectory = SharePatchFileUtil.getPatchDirectory(app); if (patchDirectory == null) { ShareTinkerLog.w(TAG, "tryCleanObsoletePatches:getPatchDirectory == null"); return; } ShareTinkerInternals.cleanPatchDirectoryWithGuard(patchDirectory, null); ShareTinkerLog.i(TAG, "tryCleanObsoletePatches: clean end, ok!"); } private boolean checkSafeModeCount(TinkerApplication application) { int count = ShareTinkerInternals.getSafeModeCount(application); if (count >= ShareConstants.TINKER_SAFE_MODE_MAX_COUNT - 1) { ShareTinkerInternals.setSafeModeCount(application, 0); return false; } application.setUseSafeMode(true); ShareTinkerInternals.setSafeModeCount(application, count + 1); return true; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourceLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Context; import android.content.Intent; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareResPatchInfo; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; /** * Created by liangwenxiang on 2016/4/14. */ public class TinkerResourceLoader { protected static final String RESOURCE_META_FILE = ShareConstants.RES_META_FILE; protected static final String RESOURCE_FILE = ShareConstants.RES_NAME; protected static final String RESOURCE_PATH = ShareConstants.RES_PATH; private static final String TAG = "Tinker.ResourceLoader"; private static ShareResPatchInfo resPatchInfo = new ShareResPatchInfo(); private TinkerResourceLoader() { } /** * Load tinker resources */ public static boolean loadTinkerResources(TinkerApplication application, String directory, Intent intentResult) { if (resPatchInfo == null || resPatchInfo.resArscMd5 == null) { return true; } String resourceString = directory + "/" + RESOURCE_PATH + "/" + RESOURCE_FILE; File resourceFile = new File(resourceString); long start = System.currentTimeMillis(); if (application.isTinkerLoadVerifyFlag()) { if (!SharePatchFileUtil.checkResourceArscMd5(resourceFile, resPatchInfo.resArscMd5)) { ShareTinkerLog.e(TAG, "Failed to load resource file, path: " + resourceFile.getPath() + ", expect md5: " + resPatchInfo.resArscMd5); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH); return false; } ShareTinkerLog.i(TAG, "verify resource file:" + resourceFile.getPath() + " md5, use time: " + (System.currentTimeMillis() - start)); } try { TinkerResourcePatcher.monkeyPatchExistingResources(application, resourceString, false); ShareTinkerLog.i(TAG, "monkeyPatchExistingResources resource file:" + resourceString + ", use time: " + (System.currentTimeMillis() - start)); } catch (Throwable e) { ShareTinkerLog.e(TAG, "install resources failed"); //remove patch dex if resource is installed failed try { SystemClassLoaderAdder.uninstallPatchDex(application.getClassLoader()); } catch (Throwable throwable) { ShareTinkerLog.e(TAG, "uninstallPatchDex failed", e); } intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION); return false; } return true; } /** * resource file exist? * fast check, only check whether exist * * @param directory * @return boolean */ public static boolean checkComplete(Context context, String directory, ShareSecurityCheck securityCheck, Intent intentResult) { String meta = securityCheck.getMetaContentMap().get(RESOURCE_META_FILE); //not found resource if (meta == null) { return true; } //only parse first line for faster ShareResPatchInfo.parseResPatchInfoFirstLine(meta, resPatchInfo); if (resPatchInfo.resArscMd5 == null) { return true; } if (!ShareResPatchInfo.checkResPatchInfo(resPatchInfo)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } String resourcePath = directory + "/" + RESOURCE_PATH + "/"; File resourceDir = new File(resourcePath); if (!resourceDir.exists() || !resourceDir.isDirectory()) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST); return false; } File resourceFile = new File(resourcePath + RESOURCE_FILE); if (!SharePatchFileUtil.isLegalFile(resourceFile)) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST); return false; } try { TinkerResourcePatcher.isResourceCanPatch(context); } catch (Throwable e) { ShareTinkerLog.e(TAG, "resource hook check failed.", e); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION); return false; } return true; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.KITKAT; import static com.tencent.tinker.loader.shareutil.ShareReflectUtil.findConstructor; import static com.tencent.tinker.loader.shareutil.ShareReflectUtil.findField; import static com.tencent.tinker.loader.shareutil.ShareReflectUtil.findMethod; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.ArrayMap; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.InputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Created by zhangshaowen on 16/9/21. * Thanks for Android Fragmentation */ class TinkerResourcePatcher { private static final String TAG = "Tinker.ResourcePatcher"; private static final String TEST_ASSETS_VALUE = "only_use_to_test_tinker_resource.txt"; // original object private static Collection> references = null; private static Map> resourceImpls = null; private static Object currentActivityThread = null; private static AssetManager newAssetManager = null; // method private static Constructor newAssetManagerCtor = null; private static Method addAssetPathMethod = null; private static Method addAssetPathAsSharedLibraryMethod = null; private static Method ensureStringBlocksMethod = null; // field private static Field assetsFiled = null; private static Field resourcesImplFiled = null; private static Field resDir = null; private static Field packagesFiled = null; private static Field resourcePackagesFiled = null; private static Field publicSourceDirField = null; private static Field stringBlocksField = null; private static long storedPatchedResModifiedTime = 0L; private static Context packageContext = null; private static Context packageResContext = null; @SuppressWarnings("unchecked") public static void isResourceCanPatch(Context context) throws Throwable { // - Replace mResDir to point to the external resource file instead of the .apk. This is // used as the asset path for new Resources objects. // - Set Application#mLoadedApk to the found LoadedApk instance // Find the ActivityThread instance for the current thread Class activityThread = Class.forName("android.app.ActivityThread"); currentActivityThread = ShareReflectUtil.getActivityThread(context, activityThread); // API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know. Class loadedApkClass; try { loadedApkClass = Class.forName("android.app.LoadedApk"); } catch (ClassNotFoundException e) { loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo"); } resDir = findField(loadedApkClass, "mResDir"); packagesFiled = findField(activityThread, "mPackages"); try { resourcePackagesFiled = findField(activityThread, "mResourcePackages"); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to get mResourcePackages field."); resourcePackagesFiled = null; } // Create a new AssetManager instance and point it to the resources final AssetManager assets = context.getAssets(); addAssetPathMethod = findMethod(assets, "addAssetPath", String.class); if (shouldAddSharedLibraryAssets(context.getApplicationInfo())) { addAssetPathAsSharedLibraryMethod = findMethod(assets, "addAssetPathAsSharedLibrary", String.class); } // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm // in L, so we do it unconditionally. try { stringBlocksField = findField(assets, "mStringBlocks"); ensureStringBlocksMethod = findMethod(assets, "ensureStringBlocks"); } catch (Throwable ignored) { // Ignored. } // Use class fetched from instance to avoid some ROMs that use customized AssetManager // class. (e.g. Baidu OS) newAssetManagerCtor = findConstructor(assets); // Iterate over all known Resources objects if (SDK_INT >= KITKAT) { //pre-N // Find the singleton instance of ResourcesManager final Class resourcesManagerClass = Class.forName("android.app.ResourcesManager"); final Method mGetInstance = findMethod(resourcesManagerClass, "getInstance"); final Object resourcesManager = mGetInstance.invoke(null); try { Field fMActiveResources = findField(resourcesManagerClass, "mActiveResources"); final ArrayMap> activeResources19 = (ArrayMap>) fMActiveResources.get(resourcesManager); references = activeResources19.values(); } catch (NoSuchFieldException ignore) { // N moved the resources to mResourceReferences final Field mResourceReferences = findField(resourcesManagerClass, "mResourceReferences"); references = (Collection>) mResourceReferences.get(resourcesManager); try { final Field mResourceImplsField = findField(resourcesManagerClass, "mResourceImpls"); resourceImpls = (Map>) mResourceImplsField.get(resourcesManager); } catch (Throwable ignored) { resourceImpls = null; } } } else { final Field fMActiveResources = findField(activityThread, "mActiveResources"); final HashMap> activeResources7 = (HashMap>) fMActiveResources.get(currentActivityThread); references = activeResources7.values(); } // check resource if (references == null) { throw new IllegalStateException("resource references is null"); } final Resources resources = context.getResources(); // fix jianGuo pro has private field 'mAssets' with Resource // try use mResourcesImpl first if (SDK_INT >= 24) { try { // N moved the mAssets inside an mResourcesImpl field resourcesImplFiled = findField(resources, "mResourcesImpl"); } catch (Throwable ignore) { // for safety assetsFiled = findField(resources, "mAssets"); } } else { assetsFiled = findField(resources, "mAssets"); } try { publicSourceDirField = findField(ApplicationInfo.class, "publicSourceDir"); } catch (NoSuchFieldException ignore) { // Ignored. } } /** * @param context * @param externalResourceFile * @throws Throwable */ public static void monkeyPatchExistingResources(Context context, String externalResourceFile, boolean isReInject) throws Throwable { if (externalResourceFile == null) { return; } final ApplicationInfo appInfo = context.getApplicationInfo(); // Prevent cached LoadedApk being recycled. packageContext = context.createPackageContext(context.getPackageName(), Context.CONTEXT_INCLUDE_CODE); packageResContext = context.createPackageContext(context.getPackageName(), 0); final Field[] packagesFields = new Field[]{packagesFiled, resourcePackagesFiled}; for (Field field : packagesFields) { if (field == null) { continue; } final Object value = field.get(currentActivityThread); for (Map.Entry> entry : ((Map>) value).entrySet()) { final Object loadedApk = entry.getValue().get(); if (loadedApk == null) { continue; } final String resDirPath = (String) resDir.get(loadedApk); if (appInfo.sourceDir.equals(resDirPath)) { resDir.set(loadedApk, externalResourceFile); } } } if (isReInject) { ShareTinkerLog.i(TAG, "Re-injecting, skip rest logic."); recordCurrentPatchedResModifiedTime(externalResourceFile); return; } newAssetManager = (AssetManager) newAssetManagerCtor.newInstance(); // Create a new AssetManager instance and point it to the resources installed under if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) { throw new IllegalStateException("Could not create new AssetManager"); } recordCurrentPatchedResModifiedTime(externalResourceFile); // Add SharedLibraries to AssetManager for resolve system resources not found issue // This influence SharedLibrary Package ID if (shouldAddSharedLibraryAssets(appInfo)) { for (String sharedLibrary : appInfo.sharedLibraryFiles) { if (!sharedLibrary.endsWith(".apk")) { continue; } if (((Integer) addAssetPathAsSharedLibraryMethod.invoke(newAssetManager, sharedLibrary)) == 0) { throw new IllegalStateException("AssetManager add SharedLibrary Fail"); } ShareTinkerLog.i(TAG, "addAssetPathAsSharedLibrary " + sharedLibrary); } } // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm // in L, so we do it unconditionally. if (stringBlocksField != null && ensureStringBlocksMethod != null) { stringBlocksField.set(newAssetManager, null); ensureStringBlocksMethod.invoke(newAssetManager); } for (WeakReference wr : references) { final Resources resources = wr.get(); if (resources == null) { continue; } // Set the AssetManager of the Resources instance to our brand new one try { //pre-N assetsFiled.set(resources, newAssetManager); } catch (Throwable ignore) { // N final Object resourceImpl = resourcesImplFiled.get(resources); // for Huawei HwResourcesImpl final Field implAssets = findField(resourceImpl, "mAssets"); implAssets.set(resourceImpl, newAssetManager); } clearPreloadTypedArrayIssue(resources); resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); } if (resourceImpls != null) { try { Field implAssetsField = null; Field mResDirField = null; for (Map.Entry> pair : resourceImpls.entrySet()) { final Object resKey = pair.getKey(); if (mResDirField == null) { mResDirField = findField(resKey.getClass(), "mResDir"); } String origResDir = (String) mResDirField.get(resKey); if (!appInfo.sourceDir.equals(origResDir)) { continue; } if (Build.VERSION.SDK_INT >= 35) { mResDirField.set(resKey, externalResourceFile); } final WeakReference resValueRef = pair.getValue(); final Object resourceImpl = resValueRef.get(); if (resourceImpl != null) { if (implAssetsField == null) { implAssetsField = findField(resourceImpl, "mAssets"); } implAssetsField.set(resourceImpl, newAssetManager); } } } catch (Throwable thr) { throw new TinkerRuntimeException("Fail to hack resourceImpls field."); } } // Handle issues caused by WebView on Android N. // Issue: On Android N, if an activity contains a webview, when screen rotates // our resource patch may lost effects. // for 5.x/6.x, we found Couldn't expand RemoteView for StatusBarNotification Exception if (Build.VERSION.SDK_INT >= 24) { try { if (publicSourceDirField != null) { publicSourceDirField.set(context.getApplicationInfo(), externalResourceFile); } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "fail to process publicSourceDirField field hack."); } } if (!checkResUpdate(context)) { throw new TinkerRuntimeException(ShareConstants.CHECK_RES_INSTALL_FAIL); } installResourceInsuranceHacks(context, externalResourceFile); } private static void installResourceInsuranceHacks(Context context, String patchedResApkPath) { try { final Object activityThread = ShareReflectUtil.getActivityThread(context, null); final Field mHField = ShareReflectUtil.findField(activityThread, "mH"); final Handler mH = (Handler) mHField.get(activityThread); final Field mCallbackField = ShareReflectUtil.findField(Handler.class, "mCallback"); final Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH); if (!(originCallback instanceof ResourceInsuranceHandlerCallback)) { final ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback( context, patchedResApkPath, originCallback, mH.getClass()); mCallbackField.set(mH, hackCallback); } else { ShareTinkerLog.w(TAG, "installResourceInsuranceHacks: already installed, skip rest logic."); } } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "failed to install resource insurance hack."); } } private static final class ResourceInsuranceHandlerCallback implements Handler.Callback { private static final String LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME = "android.app.servertransaction.LaunchActivityItem"; private final Context mContext; private final String mPatchResApkPath; private final Handler.Callback mOriginalCallback; private final int LAUNCH_ACTIVITY; private final int RELAUNCH_ACTIVITY; private final int EXECUTE_TRANSACTION; private Method mGetCallbacksMethod = null; private boolean mSkipInterceptExecuteTransaction = false; ResourceInsuranceHandlerCallback(Context context, String patchResApkPath, Handler.Callback original, Class hClazz) { Context appContext = context.getApplicationContext(); mContext = (appContext != null ? appContext : context); mPatchResApkPath = patchResApkPath; mOriginalCallback = original; LAUNCH_ACTIVITY = fetchMessageId(hClazz, "LAUNCH_ACTIVITY", 100); RELAUNCH_ACTIVITY = fetchMessageId(hClazz, "RELAUNCH_ACTIVITY", 126); if (ShareTinkerInternals.isNewerOrEqualThanVersion(28, true)) { EXECUTE_TRANSACTION = fetchMessageId(hClazz, "EXECUTE_TRANSACTION ", 159); } else { EXECUTE_TRANSACTION = -1; } } private int fetchMessageId(Class hClazz, String name, int defVal) { int value; try { value = ShareReflectUtil.findField(hClazz, name).getInt(null); } catch (Throwable e) { value = defVal; } return value; } @Override public boolean handleMessage(Message msg) { boolean consume = false; if (hackMessage(msg)) { consume = true; } else if (mOriginalCallback != null) { consume = mOriginalCallback.handleMessage(msg); } return consume; } @SuppressWarnings("unchecked") private boolean hackMessage(Message msg) { boolean shouldReInjectPatchedResources = false; if (!isPatchedResModifiedAfterLastLoad(mPatchResApkPath)) { shouldReInjectPatchedResources = false; } else { if (msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY) { shouldReInjectPatchedResources = true; } else if (msg.what == EXECUTE_TRANSACTION) { do { if (mSkipInterceptExecuteTransaction) { break; } final Object transaction = msg.obj; if (transaction == null) { ShareTinkerLog.w(TAG, "transaction is null, skip rest insurance logic."); break; } if (mGetCallbacksMethod == null) { try { mGetCallbacksMethod = ShareReflectUtil.findMethod(transaction, "getCallbacks"); } catch (Throwable ignored) { // Ignored. } } if (mGetCallbacksMethod == null) { ShareTinkerLog.e(TAG, "fail to find getLifecycleStateRequest method, skip rest insurance logic."); mSkipInterceptExecuteTransaction = true; break; } try { final List req = (List) mGetCallbacksMethod.invoke(transaction); if (req != null && req.size() > 0) { final Object cb = req.get(0); shouldReInjectPatchedResources = cb != null && cb.getClass().getName().equals(LAUNCH_ACTIVITY_LIFECYCLE_ITEM_CLASSNAME); } } catch (Throwable ignored) { ShareTinkerLog.e(TAG, "fail to call getLifecycleStateRequest method, skip rest insurance logic."); } } while (false); } } if (shouldReInjectPatchedResources) { try { monkeyPatchExistingResources(mContext, mPatchResApkPath, true); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "fail to ensure patched resources available after it's modified."); } } return false; } } private static boolean isPatchedResModifiedAfterLastLoad(String patchedResPath) { long patchedResModifiedTime; try { patchedResModifiedTime = new File(patchedResPath).lastModified(); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to get patched res modified time."); patchedResModifiedTime = 0L; } if (patchedResModifiedTime == 0) { return false; } if (patchedResModifiedTime == storedPatchedResModifiedTime) { return false; } return true; } private static void recordCurrentPatchedResModifiedTime(String patchedResPath) { try { storedPatchedResModifiedTime = new File(patchedResPath).lastModified(); } catch (Throwable thr) { ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to store patched res modified time."); storedPatchedResModifiedTime = 0L; } } /** * Why must I do these? * Resource has mTypedArrayPool field, which just like Message Poll to reduce gc * MiuiResource change TypedArray to MiuiTypedArray, but it get string block from offset instead of assetManager */ private static void clearPreloadTypedArrayIssue(Resources resources) { // Perform this trick not only in Miui system since we can't predict if any other // manufacturer would do the same modification to Android. // if (!isMiuiSystem) { // return; // } ShareTinkerLog.w(TAG, "try to clear typedArray cache!"); // Clear typedArray cache. try { final Field typedArrayPoolField = findField(Resources.class, "mTypedArrayPool"); final Object origTypedArrayPool = typedArrayPoolField.get(resources); final Method acquireMethod = findMethod(origTypedArrayPool, "acquire"); while (true) { if (acquireMethod.invoke(origTypedArrayPool) == null) { break; } } } catch (Throwable ignored) { ShareTinkerLog.e(TAG, "clearPreloadTypedArrayIssue failed, ignore error: " + ignored); } } private static boolean checkResUpdate(Context context) { InputStream is = null; try { is = context.getAssets().open(TEST_ASSETS_VALUE); } catch (Throwable e) { ShareTinkerLog.e(TAG, "checkResUpdate failed, can't find test resource assets file " + TEST_ASSETS_VALUE + " e:" + e.getMessage()); return false; } finally { SharePatchFileUtil.closeQuietly(is); } ShareTinkerLog.i(TAG, "checkResUpdate success, found test resource assets file " + TEST_ASSETS_VALUE); return true; } private static boolean shouldAddSharedLibraryAssets(ApplicationInfo applicationInfo) { return SDK_INT >= Build.VERSION_CODES.N && applicationInfo != null && applicationInfo.sharedLibraryFiles != null; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcesKey.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; /** * Created by zhangshaowen on 17/1/12. * * TODO: * Thanks for Android Fragmentation * hold the issue https://github.com/Tencent/tinker/issues/302 */ public class TinkerResourcesKey { private static final class V24 { } private static final class V19 { } private static final class V17 { } private static final class V7 { } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerRuntimeException.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; /** * Created by zhangshaowen on 16/7/8. */ public class TinkerRuntimeException extends RuntimeException { private static final String TINKER_RUNTIME_EXCEPTION_PREFIX = "Tinker Exception:"; private static final long serialVersionUID = 1L; public TinkerRuntimeException(String detailMessage) { super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage); } public TinkerRuntimeException(String detailMessage, Throwable throwable) { super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage, throwable); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerSoLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Intent; import com.tencent.tinker.loader.shareutil.ShareBsDiffPatchInfo; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import java.io.File; import java.util.ArrayList; import java.util.HashMap; /** * Created by zhangshaowen on 16/3/8. */ /** * check the complete of the dex files * pre-load patch dex files * we won't load patch library directly! */ public class TinkerSoLoader { protected static final String SO_MEAT_FILE = ShareConstants.SO_META_FILE; protected static final String SO_PATH = ShareConstants.SO_PATH; private static final String TAG = "Tinker.TinkerSoLoader"; /** * all the library files in meta file exist? * fast check, only check whether exist * * @param directory * @return boolean */ public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, Intent intentResult) { String meta = securityCheck.getMetaContentMap().get(SO_MEAT_FILE); //not found lib if (meta == null) { return true; } ArrayList libraryList = new ArrayList<>(); ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, libraryList); if (libraryList.isEmpty()) { return true; } //tinker//patch-641e634c/lib String libraryPath = directory + "/" + SO_PATH + "/"; HashMap libs = new HashMap<>(); for (ShareBsDiffPatchInfo info : libraryList) { if (!ShareBsDiffPatchInfo.checkDiffPatchInfo(info)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } String middle = info.path + "/" + info.name; //unlike dex, keep the original structure libs.put(middle, info.md5); } File libraryDir = new File(libraryPath); if (!libraryDir.exists() || !libraryDir.isDirectory()) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST); return false; } //fast check whether there is any dex files missing for (String relative : libs.keySet()) { File libFile = new File(libraryPath + relative); if (!SharePatchFileUtil.isLegalFile(libFile)) { ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST); intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH, libFile.getAbsolutePath()); return false; } } //if is ok, add to result intent intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_LIBS_PATH, libs); return true; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerTestDexLoad.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import com.tencent.tinker.anno.Keep; /** * Created by zhangshaowen on 16/9/18. */ @Keep public class TinkerTestDexLoad { public static boolean isPatch = false; } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerUncaughtHandler.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.content.Context; import android.util.Log; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * Created by zhangshaowen on 16/12/1. */ public class TinkerUncaughtHandler implements Thread.UncaughtExceptionHandler { private static final String TAG = "Tinker.UncaughtHandler"; private final File crashFile; private final Context context; private final Thread.UncaughtExceptionHandler ueh; public TinkerUncaughtHandler(Context context) { this.context = context; ueh = Thread.getDefaultUncaughtExceptionHandler(); crashFile = SharePatchFileUtil.getPatchLastCrashFile(context); } @Override public void uncaughtException(Thread thread, Throwable ex) { ShareTinkerLog.e(TAG, "TinkerUncaughtHandler catch exception:" + Log.getStackTraceString(ex)); ueh.uncaughtException(thread, ex); if (crashFile != null) { Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); //only catch real uncaught Exception if (handler instanceof TinkerUncaughtHandler) { File parentFile = crashFile.getParentFile(); if (!parentFile.exists() && !parentFile.mkdirs()) { ShareTinkerLog.e(TAG, "print crash file error: create directory fail!"); return; } PrintWriter pw = null; try { pw = new PrintWriter(new FileWriter(crashFile, false)); pw.println("process:" + ShareTinkerInternals.getProcessName(this.context)); pw.println(ShareTinkerInternals.getExceptionCauseString(ex)); } catch (IOException e) { //ignore ShareTinkerLog.e(TAG, "print crash file error:" + Log.getStackTraceString(e)); } finally { SharePatchFileUtil.closeQuietly(pw); } android.os.Process.killProcess(android.os.Process.myPid()); } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.app; import android.annotation.TargetApi; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.os.Handler; import android.os.SystemClock; import com.tencent.tinker.anno.Keep; import com.tencent.tinker.loader.TinkerLoader; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.TinkerUncaughtHandler; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerInternals; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * Created by zhangshaowen on 16/3/8. */ public abstract class TinkerApplication extends Application { private static final String INTENT_PATCH_EXCEPTION = ShareIntentUtil.INTENT_PATCH_EXCEPTION; private static final String TINKER_LOADER_METHOD = "tryLoad"; private static final TinkerApplication[] SELF_HOLDER = {null}; /** * tinkerFlags, which types is supported * dex only, library only, all support * default: TINKER_ENABLE_ALL */ private final int tinkerFlags; /** * whether verify md5 when we load dex or lib * they store at data/data/package, and we had verity them at the :patch process. * so we don't have to verity them every time for quicker! * default:false */ private final boolean tinkerLoadVerifyFlag; private final String delegateClassName; private final String loaderClassName; /** * if we have load patch, we should use safe mode */ private boolean useSafeMode; protected Intent tinkerResultIntent; protected ClassLoader mCurrentClassLoader = null; private Handler mInlineFence = null; private final boolean useDelegateLastClassLoader; private final boolean useInterpretModeOnSupported32BitSystem; protected TinkerApplication(int tinkerFlags) { this(tinkerFlags, "com.tencent.tinker.entry.DefaultApplicationLike"); } protected TinkerApplication(int tinkerFlags, String delegateClassName) { this(tinkerFlags, delegateClassName, TinkerLoader.class.getName(), false); } protected TinkerApplication(int tinkerFlags, String delegateClassName, String loaderClassName, boolean tinkerLoadVerifyFlag) { this(tinkerFlags, delegateClassName, loaderClassName, tinkerLoadVerifyFlag, true, false); } protected TinkerApplication(int tinkerFlags, String delegateClassName, String loaderClassName, boolean tinkerLoadVerifyFlag, boolean useDelegateLastClassLoader) { this(tinkerFlags, delegateClassName, loaderClassName, tinkerLoadVerifyFlag, useDelegateLastClassLoader, false); } protected TinkerApplication(int tinkerFlags, String delegateClassName, String loaderClassName, boolean tinkerLoadVerifyFlag, boolean useDelegateLastClassLoader, boolean useInterpretModeOnSupported32BitSystem) { synchronized (SELF_HOLDER) { SELF_HOLDER[0] = this; } this.tinkerFlags = tinkerFlags; this.delegateClassName = delegateClassName; this.loaderClassName = loaderClassName; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.useDelegateLastClassLoader = useDelegateLastClassLoader; this.useInterpretModeOnSupported32BitSystem = useInterpretModeOnSupported32BitSystem; } public static TinkerApplication getInstance() { synchronized (SELF_HOLDER) { if (SELF_HOLDER[0] == null) { throw new IllegalStateException("TinkerApplication is not initialized."); } return SELF_HOLDER[0]; } } private void loadTinker() { try { //reflect tinker loader, because loaderClass may be define by user! Class tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader()); Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class); Constructor constructor = tinkerLoadClass.getConstructor(); tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this); } catch (Throwable e) { //has exception, put exception error code tinkerResultIntent = new Intent(); ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION); tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e); } } private Handler createInlineFence(Application app, int tinkerFlags, String delegateClassName, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent resultIntent) { try { // Use reflection to create the delegate so it doesn't need to go into the primary dex. // And we can also patch it final Class delegateClass = Class.forName(delegateClassName, false, mCurrentClassLoader); final Constructor constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class, Intent.class); final Object appLike = constructor.newInstance(app, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, resultIntent); final Class inlineFenceClass = Class.forName( "com.tencent.tinker.entry.TinkerApplicationInlineFence", false, mCurrentClassLoader); final Class appLikeClass = Class.forName( "com.tencent.tinker.entry.ApplicationLike", false, mCurrentClassLoader); final Constructor inlineFenceCtor = inlineFenceClass.getConstructor(appLikeClass); inlineFenceCtor.setAccessible(true); return (Handler) inlineFenceCtor.newInstance(appLike); } catch (Throwable thr) { throw new TinkerRuntimeException("createInlineFence failed", thr); } } protected void onBaseContextAttached(Context base, long applicationStartElapsedTime, long applicationStartMillisTime) { try { loadTinker(); mCurrentClassLoader = base.getClassLoader(); mInlineFence = createInlineFence(this, tinkerFlags, delegateClassName, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); TinkerInlineFenceAction.callOnBaseContextAttached(mInlineFence, base); //reset save mode if (useSafeMode) { ShareTinkerInternals.setSafeModeCount(this, 0); } } catch (TinkerRuntimeException e) { throw e; } catch (Throwable thr) { throw new TinkerRuntimeException(thr.getMessage(), thr); } } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); final long applicationStartElapsedTime = SystemClock.elapsedRealtime(); final long applicationStartMillisTime = System.currentTimeMillis(); Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this)); onBaseContextAttached(base, applicationStartElapsedTime, applicationStartMillisTime); } @Override public void onCreate() { super.onCreate(); if (mInlineFence == null) { return; } TinkerInlineFenceAction.callOnCreate(mInlineFence); } @Override public void onTerminate() { super.onTerminate(); if (mInlineFence == null) { return; } TinkerInlineFenceAction.callOnTerminate(mInlineFence); } @Override public void onLowMemory() { super.onLowMemory(); if (mInlineFence == null) { return; } TinkerInlineFenceAction.callOnLowMemory(mInlineFence); } @TargetApi(14) @Override public void onTrimMemory(int level) { super.onTrimMemory(level); if (mInlineFence == null) { return; } TinkerInlineFenceAction.callOnTrimMemory(mInlineFence, level); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mInlineFence == null) { return; } TinkerInlineFenceAction.callOnConfigurationChanged(mInlineFence, newConfig); } @Override public Resources getResources() { final Resources resources = super.getResources(); if (mInlineFence == null) { return resources; } return TinkerInlineFenceAction.callGetResources(mInlineFence, resources); } @Override public ClassLoader getClassLoader() { final ClassLoader classLoader = super.getClassLoader(); if (mInlineFence == null) { return classLoader; } return TinkerInlineFenceAction.callGetClassLoader(mInlineFence, classLoader); } @Override public AssetManager getAssets() { final AssetManager assets = super.getAssets(); if (mInlineFence == null) { return assets; } return TinkerInlineFenceAction.callGetAssets(mInlineFence, assets); } @Override public Object getSystemService(String name) { final Object service = super.getSystemService(name); if (mInlineFence == null) { return service; } return TinkerInlineFenceAction.callGetSystemService(mInlineFence, name, service); } @Override public Context getBaseContext() { final Context base = super.getBaseContext(); if (mInlineFence == null) { return base; } return TinkerInlineFenceAction.callGetBaseContext(mInlineFence, base); } @Override public Theme getTheme() { final Theme theme = super.getTheme(); if (mInlineFence == null) { return theme; } return TinkerInlineFenceAction.callGetTheme(mInlineFence, theme); } @Keep public int mzNightModeUseOf() { if (mInlineFence == null) { // Return 1 for default according to MeiZu's announcement. return 1; } return TinkerInlineFenceAction.callMZNightModeUseOf(mInlineFence); } public void setUseSafeMode(boolean useSafeMode) { this.useSafeMode = useSafeMode; } public boolean isTinkerLoadVerifyFlag() { return tinkerLoadVerifyFlag; } public int getTinkerFlags() { return tinkerFlags; } public boolean isUseDelegateLastClassLoader() { return useDelegateLastClassLoader; } public boolean isUseInterpretModeOnSupported32BitSystem() { return useInterpretModeOnSupported32BitSystem; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/app/TinkerInlineFenceAction.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.app; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.os.Handler; import android.os.Message; /** * Created by tangyinsheng on 2019-11-05. */ public final class TinkerInlineFenceAction { public static final int ACTION_ON_BASE_CONTEXT_ATTACHED = 1; public static final int ACTION_ON_CREATE = 2; public static final int ACTION_ON_CONFIGURATION_CHANGED = 3; public static final int ACTION_ON_TRIM_MEMORY = 4; public static final int ACTION_ON_LOW_MEMORY = 5; public static final int ACTION_ON_TERMINATE = 6; public static final int ACTION_GET_CLASSLOADER = 7; public static final int ACTION_GET_BASE_CONTEXT = 8; public static final int ACTION_GET_ASSETS = 9; public static final int ACTION_GET_RESOURCES = 10; public static final int ACTION_GET_SYSTEM_SERVICE = 11; public static final int ACTION_MZ_NIGHTMODE_USE_OF = 12; public static final int ACTION_GET_THEME = 13; static void callOnBaseContextAttached(Handler inlineFence, Context context) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_BASE_CONTEXT_ATTACHED, context); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static void callOnCreate(Handler inlineFence) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_CREATE); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static void callOnConfigurationChanged(Handler inlineFence, Configuration newConfig) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_CONFIGURATION_CHANGED, newConfig); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static void callOnTrimMemory(Handler inlineFence, int level) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_TRIM_MEMORY, level); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static void callOnLowMemory(Handler inlineFence) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_LOW_MEMORY); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static void callOnTerminate(Handler inlineFence) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_ON_TERMINATE); inlineFence.handleMessage(msg); } finally { msg.recycle(); } } static ClassLoader callGetClassLoader(Handler inlineFence, ClassLoader cl) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_CLASSLOADER, cl); inlineFence.handleMessage(msg); return (ClassLoader) msg.obj; } finally { msg.recycle(); } } static Context callGetBaseContext(Handler inlineFence, Context base) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_BASE_CONTEXT, base); inlineFence.handleMessage(msg); return (Context) msg.obj; } finally { msg.recycle(); } } static AssetManager callGetAssets(Handler inlineFence, AssetManager assets) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_ASSETS, assets); inlineFence.handleMessage(msg); return (AssetManager) msg.obj; } finally { msg.recycle(); } } static Resources callGetResources(Handler inlineFence, Resources res) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_RESOURCES, res); inlineFence.handleMessage(msg); return (Resources) msg.obj; } finally { msg.recycle(); } } static Object callGetSystemService(Handler inlineFence, String name, Object service) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_SYSTEM_SERVICE, new Object[]{name, service}); inlineFence.handleMessage(msg); return msg.obj; } finally { msg.recycle(); } } static Theme callGetTheme(Handler inlineFence, Theme theme) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_GET_THEME, theme); inlineFence.handleMessage(msg); return (Theme) msg.obj; } finally { msg.recycle(); } } static int callMZNightModeUseOf(Handler inlineFence) { Message msg = null; try { msg = Message.obtain(inlineFence, ACTION_MZ_NIGHTMODE_USE_OF); inlineFence.handleMessage(msg); return (int) msg.obj; } finally { msg.recycle(); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/ActivityStubManager.java ================================================ package com.tencent.tinker.loader.hotplug; import android.content.pm.ActivityInfo; import java.util.HashMap; import java.util.Map; /** * Created by tangyinsheng on 2017/7/31. */ public class ActivityStubManager { private static final String TAG = "Tinker.ActivityStubManager"; private static Map sTargetToStubClassNameMap = new HashMap<>(); private static final int[] STANDARD_STUB_COUNT_SLOTS = {ActivityStubs.STANDARD_STUB_COUNT, ActivityStubs.STANDARD_TRSNAPARENT_STUB_COUNT}; private static final int[] SINGLETOP_STUB_COUNT_SLOTS = {ActivityStubs.SINGLETOP_STUB_COUNT, ActivityStubs.SINGLETOP_TRSNAPARENT_STUB_COUNT}; private static final int[] SINGLETASK_STUB_COUNT_SLOTS = {ActivityStubs.SINGLETASK_STUB_COUNT, ActivityStubs.SINGLETASK_TRSNAPARENT_STUB_COUNT}; private static final int[] SINGLEINSTANCE_STUB_COUNT_SLOTS = {ActivityStubs.SINGLEINSTANCE_STUB_COUNT, ActivityStubs.SINGLEINSTANCE_TRSNAPARENT_STUB_COUNT}; private static final int[] NEXT_STANDARD_STUB_IDX_SLOTS = {0, 0}; private static final int[] NEXT_SINGLETOP_STUB_IDX_SLOTS = {0, 0}; private static final int[] NEXT_SINGLETASK_STUB_IDX_SLOTS = {0, 0}; private static final int[] NEXT_SINGLEINSTANCE_STUB_IDX_SLOTS = {0, 0}; private static final int NOTRANSPARENT_SLOT_INDEX = 0; private static final int TRANSPARENT_SLOT_INDEX = 1; public static String assignStub(String targetClassName, int launchMode, boolean isTransparent) { String stubClassName = sTargetToStubClassNameMap.get(targetClassName); if (stubClassName != null) { return stubClassName; } String stubNameFormat; final int[] nextStubIdxSlots; final int[] countSlots; final int slotIdx; switch (launchMode) { case ActivityInfo.LAUNCH_SINGLE_TOP: { stubNameFormat = ActivityStubs.SINGLETOP_STUB_CLASSNAME_FORMAT; nextStubIdxSlots = NEXT_SINGLETOP_STUB_IDX_SLOTS; countSlots = SINGLETOP_STUB_COUNT_SLOTS; break; } case ActivityInfo.LAUNCH_SINGLE_TASK: { stubNameFormat = ActivityStubs.SINGLETASK_STUB_CLASSNAME_FORMAT; nextStubIdxSlots = NEXT_SINGLETASK_STUB_IDX_SLOTS; countSlots = SINGLETASK_STUB_COUNT_SLOTS; break; } case ActivityInfo.LAUNCH_SINGLE_INSTANCE: { stubNameFormat = ActivityStubs.SINGLEINSTANCE_STUB_CLASSNAME_FORMAT; nextStubIdxSlots = NEXT_SINGLEINSTANCE_STUB_IDX_SLOTS; countSlots = SINGLEINSTANCE_STUB_COUNT_SLOTS; break; } case ActivityInfo.LAUNCH_MULTIPLE: default: { stubNameFormat = ActivityStubs.STARDARD_STUB_CLASSNAME_FORMAT; nextStubIdxSlots = NEXT_STANDARD_STUB_IDX_SLOTS; countSlots = STANDARD_STUB_COUNT_SLOTS; break; } } if (isTransparent) { stubNameFormat += ActivityStubs.TRANSPARENT_STUB_FORMAT_SUFFIX; slotIdx = TRANSPARENT_SLOT_INDEX; } else { slotIdx = NOTRANSPARENT_SLOT_INDEX; } int stubIndex = nextStubIdxSlots[slotIdx]++; if (stubIndex >= countSlots[slotIdx]) { stubIndex = nextStubIdxSlots[slotIdx] = 0; } stubClassName = String.format(stubNameFormat, stubIndex); sTargetToStubClassNameMap.put(targetClassName, stubClassName); return stubClassName; } private ActivityStubManager() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/ActivityStubs.java ================================================ package com.tencent.tinker.loader.hotplug; import android.app.Activity; /** * Created by tangyinsheng on 2017/8/3. */ final class ActivityStubs { static final String STUB_PACKAGE_NAME = ActivityStubs.class.getPackage().getName(); static final String STARDARD_STUB_CLASSNAME_FORMAT = STUB_PACKAGE_NAME + "." + ActivityStubs.class.getSimpleName() + "$STDStub_%02X"; static final String SINGLETOP_STUB_CLASSNAME_FORMAT = STUB_PACKAGE_NAME + "." + ActivityStubs.class.getSimpleName() + "$SGTStub_%02X"; static final String SINGLETASK_STUB_CLASSNAME_FORMAT = STUB_PACKAGE_NAME + "." + ActivityStubs.class.getSimpleName() + "$SGTKStub_%02X"; static final String SINGLEINSTANCE_STUB_CLASSNAME_FORMAT = STUB_PACKAGE_NAME + "." + ActivityStubs.class.getSimpleName() + "$SIStub_%02X"; static final String TRANSPARENT_STUB_FORMAT_SUFFIX = "_T"; static final int STANDARD_STUB_COUNT = 10; static final int STANDARD_TRSNAPARENT_STUB_COUNT = 3; static final int SINGLETOP_STUB_COUNT = 10; static final int SINGLETOP_TRSNAPARENT_STUB_COUNT = 3; static final int SINGLETASK_STUB_COUNT = 10; static final int SINGLETASK_TRSNAPARENT_STUB_COUNT = 3; static final int SINGLEINSTANCE_STUB_COUNT = 10; static final int SINGLEINSTANCE_TRSNAPARENT_STUB_COUNT = 3; public static final class STDStub_00 extends Activity { } public static final class STDStub_01 extends Activity { } public static final class STDStub_02 extends Activity { } public static final class STDStub_03 extends Activity { } public static final class STDStub_04 extends Activity { } public static final class STDStub_05 extends Activity { } public static final class STDStub_06 extends Activity { } public static final class STDStub_07 extends Activity { } public static final class STDStub_08 extends Activity { } public static final class STDStub_09 extends Activity { } public static final class STDStub_00_T extends Activity { } public static final class STDStub_01_T extends Activity { } public static final class STDStub_02_T extends Activity { } public static final class SGTStub_00 extends Activity { } public static final class SGTStub_01 extends Activity { } public static final class SGTStub_02 extends Activity { } public static final class SGTStub_03 extends Activity { } public static final class SGTStub_04 extends Activity { } public static final class SGTStub_05 extends Activity { } public static final class SGTStub_06 extends Activity { } public static final class SGTStub_07 extends Activity { } public static final class SGTStub_08 extends Activity { } public static final class SGTStub_09 extends Activity { } public static final class SGTStub_00_T extends Activity { } public static final class SGTStub_01_T extends Activity { } public static final class SGTStub_02_T extends Activity { } public static final class SGTKStub_00 extends Activity { } public static final class SGTKStub_01 extends Activity { } public static final class SGTKStub_02 extends Activity { } public static final class SGTKStub_03 extends Activity { } public static final class SGTKStub_04 extends Activity { } public static final class SGTKStub_05 extends Activity { } public static final class SGTKStub_06 extends Activity { } public static final class SGTKStub_07 extends Activity { } public static final class SGTKStub_08 extends Activity { } public static final class SGTKStub_09 extends Activity { } public static final class SGTKStub_00_T extends Activity { } public static final class SGTKStub_01_T extends Activity { } public static final class SGTKStub_02_T extends Activity { } public static final class SIStub_00 extends Activity { } public static final class SIStub_01 extends Activity { } public static final class SIStub_02 extends Activity { } public static final class SIStub_03 extends Activity { } public static final class SIStub_04 extends Activity { } public static final class SIStub_05 extends Activity { } public static final class SIStub_06 extends Activity { } public static final class SIStub_07 extends Activity { } public static final class SIStub_08 extends Activity { } public static final class SIStub_09 extends Activity { } public static final class SIStub_00_T extends Activity { } public static final class SIStub_01_T extends Activity { } public static final class SIStub_02_T extends Activity { } private ActivityStubs() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/ComponentHotplug.java ================================================ package com.tencent.tinker.loader.hotplug; import android.content.Context; import android.os.Build; import android.os.Handler; import com.tencent.tinker.loader.app.TinkerApplication; import com.tencent.tinker.loader.hotplug.handler.AMSInterceptHandler; import com.tencent.tinker.loader.hotplug.handler.MHMessageHandler; import com.tencent.tinker.loader.hotplug.handler.PMSInterceptHandler; import com.tencent.tinker.loader.hotplug.interceptor.HandlerMessageInterceptor; import com.tencent.tinker.loader.hotplug.interceptor.ServiceBinderInterceptor; import com.tencent.tinker.loader.hotplug.interceptor.TinkerHackInstrumentation; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.Field; /** * Created by tangyinsheng on 2017/7/31. */ public final class ComponentHotplug { private static final String TAG = "Tinker.ComponentHotplug"; private static volatile boolean sInstalled = false; private static ServiceBinderInterceptor sAMSInterceptor; private static ServiceBinderInterceptor sPMSInterceptor; private static HandlerMessageInterceptor sMHMessageInterceptor; private static TinkerHackInstrumentation sTinkerHackInstrumentation; public synchronized static void install(TinkerApplication app, ShareSecurityCheck checker) throws UnsupportedEnvironmentException { if (!sInstalled) { try { if (IncrementComponentManager.init(app, checker)) { sAMSInterceptor = new ServiceBinderInterceptor(app, EnvConsts.ACTIVITY_MANAGER_SRVNAME, new AMSInterceptHandler(app)); sPMSInterceptor = new ServiceBinderInterceptor(app, EnvConsts.PACKAGE_MANAGER_SRVNAME, new PMSInterceptHandler()); sAMSInterceptor.install(); sPMSInterceptor.install(); if (Build.VERSION.SDK_INT < 27) { final Handler mH = fetchMHInstance(app); sMHMessageInterceptor = new HandlerMessageInterceptor(mH, new MHMessageHandler(app)); sMHMessageInterceptor.install(); } else { sTinkerHackInstrumentation = TinkerHackInstrumentation.create(app); sTinkerHackInstrumentation.install(); } sInstalled = true; ShareTinkerLog.i(TAG, "installed successfully."); } } catch (Throwable thr) { uninstall(); throw new UnsupportedEnvironmentException(thr); } } } public synchronized static void ensureComponentHotplugInstalled(TinkerApplication app) throws UnsupportedEnvironmentException { // Some environments may reset AMS, PMS and mH,which cause component hotplug feature // being unavailable. So we reinstall them here. if (sInstalled) { try { sAMSInterceptor.install(); sPMSInterceptor.install(); if (Build.VERSION.SDK_INT < 27) { sMHMessageInterceptor.install(); } else { sTinkerHackInstrumentation.install(); } } catch (Throwable thr) { uninstall(); throw new UnsupportedEnvironmentException(thr); } } else { ShareTinkerLog.i(TAG, "method install() is not invoked, ignore ensuring operations."); } } private static Handler fetchMHInstance(Context context) { final Object activityThread = ShareReflectUtil.getActivityThread(context, null); if (activityThread == null) { throw new IllegalStateException("failed to fetch instance of ActivityThread."); } try { final Field mHField = ShareReflectUtil.findField(activityThread, "mH"); final Handler mH = (Handler) mHField.get(activityThread); return mH; } catch (Throwable thr) { throw new IllegalStateException(thr); } } public synchronized static void uninstall() { if (sInstalled) { try { sAMSInterceptor.uninstall(); sPMSInterceptor.uninstall(); if (Build.VERSION.SDK_INT < 27) { sMHMessageInterceptor.uninstall(); } else { sTinkerHackInstrumentation.uninstall(); } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "exception when uninstall.", thr); } sInstalled = false; } } private ComponentHotplug() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/EnvConsts.java ================================================ package com.tencent.tinker.loader.hotplug; import android.content.Context; /** * Created by tangyinsheng on 2017/8/3. */ public final class EnvConsts { public static final String ACTIVITY_MANAGER_SRVNAME = Context.ACTIVITY_SERVICE; public static final String PACKAGE_MANAGER_SRVNAME = "package"; public static final String INTENT_EXTRA_OLD_COMPONENT = "tinker_iek_old_component"; // Please keep it synchronized with the other one defined in 'TypedValue' class public static final String INCCOMPONENT_META_FILE = "assets/inc_component_meta.txt"; private EnvConsts() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/IncrementComponentManager.java ================================================ package com.tencent.tinker.loader.hotplug; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; import android.text.TextUtils; import android.util.Log; import android.util.Xml; import com.tencent.tinker.loader.shareutil.SharePatchFileUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareSecurityCheck; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * Created by tangyinsheng on 2017/8/3. */ public final class IncrementComponentManager { private static final String TAG = "Tinker.IncrementCompMgr"; private static final int TAG_ACTIVITY = 0; private static final int TAG_SERVICE = 1; private static final int TAG_PROVIDER = 2; private static final int TAG_RECEIVER = 3; private static Context sContext = null; private static String sPackageName = null; private static volatile boolean sInitialized = false; private static final Map CLASS_NAME_TO_ACTIVITY_INFO_MAP = new HashMap<>(); private static final Map CLASS_NAME_TO_INTENT_FILTER_MAP = new HashMap<>(); private static abstract class AttrTranslator { final void translate(Context context, int tagType, XmlPullParser parser, T_RESULT result) { onInit(context, tagType, parser); final int attrCount = parser.getAttributeCount(); for (int i = 0; i < attrCount; ++i) { final String attrPrefix = parser.getAttributePrefix(i); if (!"android".equals(attrPrefix)) { continue; } final String attrName = parser.getAttributeName(i); final String attrValue = parser.getAttributeValue(i); onTranslate(context, tagType, attrName, attrValue, result); } } void onInit(Context context, int tagType, XmlPullParser parser) { // Do nothing. } abstract void onTranslate(Context context, int tagType, String attrName, String attrValue, T_RESULT result); } private static final AttrTranslator ACTIVITY_INFO_ATTR_TRANSLATOR = new AttrTranslator() { @Override void onInit(Context context, int tagType, XmlPullParser parser) { try { if (tagType == TAG_ACTIVITY && (parser.getEventType() != XmlPullParser.START_TAG || !"activity".equals(parser.getName()))) { throw new IllegalStateException("unexpected xml parser state when parsing incremental component manifest."); } } catch (XmlPullParserException e) { throw new IllegalStateException(e); } } @Override void onTranslate(Context context, int tagType, String attrName, String attrValue, ActivityInfo result) { if ("name".equals(attrName)) { if (attrValue.charAt(0) == '.') { result.name = context.getPackageName() + attrValue; } else { result.name = attrValue; } } else if ("parentActivityName".equals(attrName)) { if (Build.VERSION.SDK_INT >= 16) { if (attrValue.charAt(0) == '.') { result.parentActivityName = context.getPackageName() + attrValue; } else { result.parentActivityName = attrValue; } } } else if ("exported".equals(attrName)) { result.exported = "true".equalsIgnoreCase(attrValue); } else if ("launchMode".equals(attrName)) { result.launchMode = parseLaunchMode(attrValue); } else if ("theme".equals(attrName)) { final Resources res = context.getResources(); final String packageName = context.getPackageName(); result.theme = res.getIdentifier(attrValue, "style", packageName); } else if ("uiOptions".equals(attrName)) { if (Build.VERSION.SDK_INT >= 14) { result.uiOptions = Integer.decode(attrValue); } } else if ("permission".equals(attrName)) { result.permission = attrValue; } else if ("taskAffinity".equals(attrName)) { result.taskAffinity = attrValue; } else if ("multiprocess".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_MULTIPROCESS; } else { result.flags &= ~ActivityInfo.FLAG_MULTIPROCESS; } } else if ("finishOnTaskLaunch".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH; } else { result.flags &= ~ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH; } } else if ("clearTaskOnLaunch".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH; } else { result.flags &= ~ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH; } } else if ("noHistory".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_NO_HISTORY; } else { result.flags &= ~ActivityInfo.FLAG_NO_HISTORY; } } else if ("alwaysRetainTaskState".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE; } else { result.flags &= ~ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE; } } else if ("stateNotNeeded".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED; } else { result.flags &= ~ActivityInfo.FLAG_STATE_NOT_NEEDED; } } else if ("excludeFromRecents".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; } else { result.flags &= ~ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; } } else if ("allowTaskReparenting".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING; } else { result.flags &= ~ActivityInfo.FLAG_ALLOW_TASK_REPARENTING; } } else if ("finishOnCloseSystemDialogs".equals(attrName)) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; } else { result.flags &= ~ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; } } else if ("showOnLockScreen".equals(attrName) || "showForAllUsers".equals(attrName)) { if (Build.VERSION.SDK_INT >= 23) { final int flag = ShareReflectUtil .getValueOfStaticIntField(ActivityInfo.class, "FLAG_SHOW_FOR_ALL_USERS", 0); if ("true".equalsIgnoreCase(attrValue)) { result.flags |= flag; } else { result.flags &= ~flag; } } } else if ("immersive".equals(attrName)) { if (Build.VERSION.SDK_INT >= 18) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_IMMERSIVE; } else { result.flags &= ~ActivityInfo.FLAG_IMMERSIVE; } } } else if ("hardwareAccelerated".equals(attrName)) { if (Build.VERSION.SDK_INT >= 11) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; } else { result.flags &= ~ActivityInfo.FLAG_HARDWARE_ACCELERATED; } } } else if ("documentLaunchMode".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { result.documentLaunchMode = Integer.decode(attrValue); } } else if ("maxRecents".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { result.maxRecents = Integer.decode(attrValue); } } else if ("configChanges".equals(attrName)) { result.configChanges = Integer.decode(attrValue); } else if ("windowSoftInputMode".equals(attrName)) { result.softInputMode = Integer.decode(attrValue); } else if ("persistableMode".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { result.persistableMode = Integer.decode(attrValue); } } else if ("allowEmbedded".equals(attrName)) { final int flag = ShareReflectUtil .getValueOfStaticIntField(ActivityInfo.class, "FLAG_ALLOW_EMBEDDED", 0); if ("true".equalsIgnoreCase(attrValue)) { result.flags |= flag; } else { result.flags &= ~flag; } } else if ("autoRemoveFromRecents".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; } else { result.flags &= ~ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; } } } else if ("relinquishTaskIdentity".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; } else { result.flags &= ~ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; } } } else if ("resumeWhilePausing".equals(attrName)) { if (Build.VERSION.SDK_INT >= 21) { if ("true".equalsIgnoreCase(attrValue)) { result.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING; } else { result.flags &= ~ActivityInfo.FLAG_RESUME_WHILE_PAUSING; } } } else if ("screenOrientation".equals(attrName)) { result.screenOrientation = parseScreenOrientation(attrValue); } else if ("label".equals(attrName)) { final String strOrResId = attrValue; int id = 0; try { id = context.getResources().getIdentifier(strOrResId, "string", sPackageName); } catch (Throwable ignored) { // Ignored. } if (id != 0) { result.labelRes = id; } else { result.nonLocalizedLabel = strOrResId; } } else if ("icon".equals(attrName)) { try { result.icon = context.getResources().getIdentifier(attrValue, null, sPackageName); } catch (Throwable ignored) { // Ignored. } } else if ("banner".equals(attrName)) { if (Build.VERSION.SDK_INT >= 20) { try { result.banner = context.getResources().getIdentifier(attrValue, null, sPackageName); } catch (Throwable ignored) { // Ignored. } } } else if ("logo".equals(attrName)) { try { result.logo = context.getResources().getIdentifier(attrValue, null, sPackageName); } catch (Throwable ignored) { // Ignored. } } } private int parseLaunchMode(String attrValue) { if ("standard".equalsIgnoreCase(attrValue)) { return ActivityInfo.LAUNCH_MULTIPLE; } else if ("singleTop".equalsIgnoreCase(attrValue)) { return ActivityInfo.LAUNCH_SINGLE_TOP; } else if ("singleTask".equalsIgnoreCase(attrValue)) { return ActivityInfo.LAUNCH_SINGLE_TASK; } else if ("singleInstance".equalsIgnoreCase(attrValue)) { return ActivityInfo.LAUNCH_SINGLE_INSTANCE; } else { ShareTinkerLog.w(TAG, "Unknown launchMode: " + attrValue); return ActivityInfo.LAUNCH_MULTIPLE; } } private int parseScreenOrientation(String attrValue) { if ("unspecified".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } else if ("behind".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_BEHIND; } else if ("landscape".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if ("portrait".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if ("reverseLandscape".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; } else if ("reversePortrait".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; } else if ("sensorLandscape".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; } else if ("sensorPortrait".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; } else if ("sensor".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_SENSOR; } else if ("fullSensor".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; } else if ("nosensor".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; } else if ("user".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_USER; } else if (Build.VERSION.SDK_INT >= 18 && "fullUser".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else if (Build.VERSION.SDK_INT >= 18 && "locked".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_LOCKED; } else if (Build.VERSION.SDK_INT >= 18 && "userLandscape".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; } else if (Build.VERSION.SDK_INT >= 18 && "userPortrait".equalsIgnoreCase(attrValue)) { return ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; } else { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } } }; public static synchronized boolean init(Context context, ShareSecurityCheck checker) throws IOException { if (!checker.getMetaContentMap().containsKey(EnvConsts.INCCOMPONENT_META_FILE)) { ShareTinkerLog.i(TAG, "package has no incremental component meta, skip init."); return false; } while (context instanceof ContextWrapper) { final Context baseCtx = ((ContextWrapper) context).getBaseContext(); if (baseCtx == null) { break; } context = baseCtx; } sContext = context; sPackageName = context.getPackageName(); final String xmlMeta = checker.getMetaContentMap().get(EnvConsts.INCCOMPONENT_META_FILE); StringReader sr = new StringReader(xmlMeta); XmlPullParser parser = null; try { parser = Xml.newPullParser(); parser.setInput(sr); int event = parser.getEventType(); while (event != XmlPullParser.END_DOCUMENT) { switch (event) { case XmlPullParser.START_TAG: final String tagName = parser.getName(); if ("activity".equalsIgnoreCase(tagName)) { final ActivityInfo aInfo = parseActivity(context, parser); CLASS_NAME_TO_ACTIVITY_INFO_MAP.put(aInfo.name, aInfo); } else if ("service".equalsIgnoreCase(tagName)) { // TODO support service component. } else if ("receiver".equalsIgnoreCase(tagName)) { // TODO support receiver component. } else if ("provider".equalsIgnoreCase(tagName)) { // TODO support provider component. } break; default: break; } event = parser.next(); } sInitialized = true; return true; } catch (XmlPullParserException e) { throw new IOException(e); } finally { if (parser != null) { try { parser.setInput(null); } catch (Throwable ignored) { // Ignored. } } SharePatchFileUtil.closeQuietly(sr); } } @SuppressWarnings("unchecked") private static synchronized ActivityInfo parseActivity(Context context, XmlPullParser parser) throws XmlPullParserException, IOException { final ActivityInfo aInfo = new ActivityInfo(); final ApplicationInfo appInfo = context.getApplicationInfo(); aInfo.applicationInfo = appInfo; aInfo.packageName = sPackageName; aInfo.processName = appInfo.processName; aInfo.launchMode = ActivityInfo.LAUNCH_MULTIPLE; aInfo.permission = appInfo.permission; aInfo.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; aInfo.taskAffinity = appInfo.taskAffinity; if (Build.VERSION.SDK_INT >= 11 && (appInfo.flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { aInfo.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; } if (Build.VERSION.SDK_INT >= 21) { aInfo.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE; } if (Build.VERSION.SDK_INT >= 14) { aInfo.uiOptions = appInfo.uiOptions; } ACTIVITY_INFO_ATTR_TRANSLATOR.translate(context, TAG_ACTIVITY, parser, aInfo); final int outerDepth = parser.getDepth(); while (true) { final int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT || (type == XmlPullParser.END_TAG && parser.getDepth() <= outerDepth)) { break; } else if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } final String tagName = parser.getName(); if ("intent-filter".equalsIgnoreCase(tagName)) { parseIntentFilter(context, aInfo.name, parser); } else if ("meta-data".equalsIgnoreCase(tagName)) { parseMetaData(context, aInfo, parser); } } return aInfo; } private static synchronized void parseIntentFilter(Context context, String componentName, XmlPullParser parser) throws XmlPullParserException, IOException { final IntentFilter intentFilter = new IntentFilter(); final String priorityStr = parser.getAttributeValue(null, "priority"); if (!TextUtils.isEmpty(priorityStr)) { intentFilter.setPriority(Integer.decode(priorityStr)); } final String autoVerify = parser.getAttributeValue(null, "autoVerify"); if (!TextUtils.isEmpty(autoVerify)) { try { final Method setAutoVerifyMethod = ShareReflectUtil.findMethod(IntentFilter.class, "setAutoVerify", boolean.class); setAutoVerifyMethod.invoke(intentFilter, "true".equalsIgnoreCase(autoVerify)); } catch (Throwable ignored) { // Ignored. } } final int outerDepth = parser.getDepth(); while (true) { final int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT || (type == XmlPullParser.END_TAG && parser.getDepth() <= outerDepth)) { break; } else if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } final String tagName = parser.getName(); if ("action".equals(tagName)) { final String name = parser.getAttributeValue(null, "name"); if (name != null) { intentFilter.addAction(name); } } else if ("category".equals(tagName)) { final String name = parser.getAttributeValue(null, "name"); if (name != null) { intentFilter.addCategory(name); } } else if ("data".equals(tagName)) { final String mimeType = parser.getAttributeValue(null, "mimeType"); if (mimeType != null) { try { intentFilter.addDataType(mimeType); } catch (IntentFilter.MalformedMimeTypeException e) { throw new XmlPullParserException("bad mimeType", parser, e); } } final String scheme = parser.getAttributeValue(null, "scheme"); if (scheme != null) { intentFilter.addDataScheme(scheme); } if (Build.VERSION.SDK_INT >= 19) { final String ssp = parser.getAttributeValue(null, "ssp"); if (ssp != null) { intentFilter.addDataSchemeSpecificPart(ssp, PatternMatcher.PATTERN_LITERAL); } final String sspPrefix = parser.getAttributeValue(null, "sspPrefix"); if (sspPrefix != null) { intentFilter.addDataSchemeSpecificPart(sspPrefix, PatternMatcher.PATTERN_PREFIX); } final String sspPattern = parser.getAttributeValue(null, "sspPattern"); if (sspPattern != null) { intentFilter.addDataSchemeSpecificPart(sspPattern, PatternMatcher.PATTERN_SIMPLE_GLOB); } } final String host = parser.getAttributeValue(null, "host"); final String port = parser.getAttributeValue(null, "port"); if (host != null) { intentFilter.addDataAuthority(host, port); } final String path = parser.getAttributeValue(null, "path"); if (path != null) { intentFilter.addDataPath(path, PatternMatcher.PATTERN_LITERAL); } final String pathPrefix = parser.getAttributeValue(null, "pathPrefix"); if (pathPrefix != null) { intentFilter.addDataPath(pathPrefix, PatternMatcher.PATTERN_PREFIX); } final String pathPattern = parser.getAttributeValue(null, "pathPattern"); if (pathPattern != null) { intentFilter.addDataPath(pathPattern, PatternMatcher.PATTERN_SIMPLE_GLOB); } } skipCurrentTag(parser); } CLASS_NAME_TO_INTENT_FILTER_MAP.put(componentName, intentFilter); } private static synchronized void parseMetaData(Context context, ActivityInfo aInfo, XmlPullParser parser) throws XmlPullParserException, IOException { final ClassLoader myCl = IncrementComponentManager.class.getClassLoader(); final String name = parser.getAttributeValue(null, "name"); final String value = parser.getAttributeValue(null, "value"); if (!TextUtils.isEmpty(name)) { if (aInfo.metaData == null) { aInfo.metaData = new Bundle(myCl); } aInfo.metaData.putString(name, value); } } private static void skipCurrentTag(XmlPullParser parser) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { } } private static synchronized void ensureInitialized() { if (!sInitialized) { throw new IllegalStateException("Not initialized!!"); } } public static boolean isIncrementActivity(String className) { ensureInitialized(); return className != null && CLASS_NAME_TO_ACTIVITY_INFO_MAP.containsKey(className); } public static ActivityInfo queryActivityInfo(String className) { ensureInitialized(); return (className != null ? CLASS_NAME_TO_ACTIVITY_INFO_MAP.get(className) : null); } // TODO needs to support rest type of components. public static ResolveInfo resolveIntent(Intent intent) { ensureInitialized(); int maxPriority = -1; String bestComponentName = null; IntentFilter respFilter = null; int bestMatchRes = 0; final ComponentName component = intent.getComponent(); if (component != null) { final String compName = component.getClassName(); if (CLASS_NAME_TO_ACTIVITY_INFO_MAP.containsKey(compName)) { bestComponentName = compName; maxPriority = 0; } } else { for (Map.Entry item : CLASS_NAME_TO_INTENT_FILTER_MAP.entrySet()) { final String componentName = item.getKey(); final IntentFilter intentFilter = item.getValue(); final int matchRes = intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(), intent.getCategories(), TAG); final boolean matches = (matchRes != IntentFilter.NO_MATCH_ACTION) && (matchRes != IntentFilter.NO_MATCH_CATEGORY) && (matchRes != IntentFilter.NO_MATCH_DATA) && (matchRes != IntentFilter.NO_MATCH_TYPE); final int priority = intentFilter.getPriority(); if (matches && priority > maxPriority) { maxPriority = priority; bestComponentName = componentName; respFilter = intentFilter; bestMatchRes = matchRes; } } } if (bestComponentName != null) { final ResolveInfo result = new ResolveInfo(); result.activityInfo = CLASS_NAME_TO_ACTIVITY_INFO_MAP.get(bestComponentName); result.filter = respFilter; result.match = bestMatchRes; result.priority = maxPriority; result.resolvePackageName = sPackageName; result.icon = result.activityInfo.icon; result.labelRes = result.activityInfo.labelRes; return result; } else { return null; } } private IncrementComponentManager() { throw new UnsupportedOperationException(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/UnsupportedEnvironmentException.java ================================================ package com.tencent.tinker.loader.hotplug; /** * Created by tangyinsheng on 2017/7/31. */ public class UnsupportedEnvironmentException extends UnsupportedOperationException { public UnsupportedEnvironmentException(String msg) { super(msg); } public UnsupportedEnvironmentException(Throwable thr) { super(thr); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/handler/AMSInterceptHandler.java ================================================ package com.tencent.tinker.loader.hotplug.handler; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Build; import com.tencent.tinker.loader.hotplug.ActivityStubManager; import com.tencent.tinker.loader.hotplug.EnvConsts; import com.tencent.tinker.loader.hotplug.IncrementComponentManager; import com.tencent.tinker.loader.hotplug.interceptor.ServiceBinderInterceptor.BinderInvocationHandler; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import java.lang.reflect.Method; /** * Created by tangyinsheng on 2017/7/31. */ public class AMSInterceptHandler implements BinderInvocationHandler { private static final String TAG = "Tinker.AMSIntrcptHndlr"; private static final int[] TRANSLUCENT_ATTR_ID = {android.R.attr.windowIsTranslucent}; private static final int INTENT_SENDER_ACTIVITY; static { // Hardcoded Value. int val = 2; if (Build.VERSION.SDK_INT < 27) { try { val = (int) ShareReflectUtil.findField(ActivityManager.class, "INTENT_SENDER_ACTIVITY").get(null); } catch (Throwable thr) { thr.printStackTrace(); val = 2; } } INTENT_SENDER_ACTIVITY = val; } private final Context mContext; public AMSInterceptHandler(Context context) { while (context instanceof ContextWrapper) { final Context baseCtx = ((ContextWrapper) context).getBaseContext(); if (baseCtx == null) { break; } context = baseCtx; } mContext = context; } @Override public Object invoke(Object target, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); if ("startActivity".equals(methodName)) { return handleStartActivity(target, method, args); } else if ("startActivities".equals(methodName)) { return handleStartActivities(target, method, args); } else if ("startActivityAndWait".equals(methodName)) { return handleStartActivity(target, method, args); } else if ("startActivityWithConfig".equals(methodName)) { return handleStartActivity(target, method, args); } else if ("startActivityAsUser".equals(methodName)) { return handleStartActivity(target, method, args); } else if ("getIntentSender".equals(methodName)) { return handleGetIntentSender(target, method, args); } return method.invoke(target, args); } private Object handleStartActivity(Object target, Method method, Object[] args) throws Throwable { int intentIdx = -1; for (int i = 0; i < args.length; ++i) { if (args[i] instanceof Intent) { intentIdx = i; break; } } if (intentIdx != -1) { final Intent newIntent = new Intent((Intent) args[intentIdx]); processActivityIntent(newIntent); args[intentIdx] = newIntent; } return method.invoke(target, args); } private Object handleStartActivities(Object target, Method method, Object[] args) throws Throwable { int intentArrIdx = -1; for (int i = 0; i < args.length; ++i) { if (args[i] instanceof Intent[]) { intentArrIdx = i; break; } } if (intentArrIdx != -1) { final Intent[] oldIntentArr = (Intent[]) args[intentArrIdx]; for (int i = 0; i < oldIntentArr.length; ++i) { final Intent newIntent = new Intent(oldIntentArr[i]); processActivityIntent(newIntent); oldIntentArr[i] = newIntent; } } return method.invoke(target, args); } private Object handleGetIntentSender(Object target, Method method, Object[] args) throws Throwable { int intentArrIdx = -1; for (int i = 0; i < args.length; ++i) { if (args[i] instanceof Intent[]) { intentArrIdx = i; break; } } if (intentArrIdx != -1) { final int intentType = (int) args[0]; if (intentType == INTENT_SENDER_ACTIVITY) { final Intent[] oldIntentArr = (Intent[]) args[intentArrIdx]; for (int i = 0; i < oldIntentArr.length; ++i) { final Intent newIntent = new Intent(oldIntentArr[i]); processActivityIntent(newIntent); oldIntentArr[i] = newIntent; } } } return method.invoke(target, args); } private void processActivityIntent(Intent intent) { String origPackageName = null; String origClassName = null; if (intent.getComponent() != null) { origPackageName = intent.getComponent().getPackageName(); origClassName = intent.getComponent().getClassName(); } else { ResolveInfo rInfo = mContext.getPackageManager().resolveActivity(intent, 0); if (rInfo == null) { rInfo = IncrementComponentManager.resolveIntent(intent); } if (rInfo != null && rInfo.filter != null && rInfo.filter.hasCategory(Intent.CATEGORY_DEFAULT)) { origPackageName = rInfo.activityInfo.packageName; origClassName = rInfo.activityInfo.name; } } if (IncrementComponentManager.isIncrementActivity(origClassName)) { final ActivityInfo origInfo = IncrementComponentManager.queryActivityInfo(origClassName); final boolean isTransparent = hasTransparentTheme(origInfo); final String stubClassName = ActivityStubManager.assignStub(origClassName, origInfo.launchMode, isTransparent); storeAndReplaceOriginalComponentName(intent, origPackageName, origClassName, stubClassName); } } private void storeAndReplaceOriginalComponentName(Intent intent, String origPackageName, String origClassName, String stubClassName) { final ComponentName origComponentName = new ComponentName(origPackageName, origClassName); ShareIntentUtil.fixIntentClassLoader(intent, mContext.getClassLoader()); intent.putExtra(EnvConsts.INTENT_EXTRA_OLD_COMPONENT, origComponentName); final ComponentName stubComponentName = new ComponentName(origPackageName, stubClassName); intent.setComponent(stubComponentName); } private boolean hasTransparentTheme(ActivityInfo activityInfo) { final int theme = activityInfo.getThemeResource(); final Resources.Theme themeObj = mContext.getResources().newTheme(); themeObj.applyStyle(theme, true); TypedArray ta = null; try { ta = themeObj.obtainStyledAttributes(TRANSLUCENT_ATTR_ID); return ta.getBoolean(0, false); } catch (Throwable thr) { return false; } finally { if (ta != null) { ta.recycle(); } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/handler/MHMessageHandler.java ================================================ package com.tencent.tinker.loader.hotplug.handler; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Build; import android.os.IBinder; import android.os.Message; import com.tencent.tinker.loader.hotplug.EnvConsts; import com.tencent.tinker.loader.hotplug.IncrementComponentManager; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import static com.tencent.tinker.loader.hotplug.interceptor.HandlerMessageInterceptor.MessageHandler; /** * Created by tangyinsheng on 2017/7/31. */ public class MHMessageHandler implements MessageHandler { private static final String TAG = "Tinker.MHMsgHndlr"; private static final int LAUNCH_ACTIVITY; static { // Hardcoded Value. int launchActivity = 100; if (Build.VERSION.SDK_INT < 27) { try { final Class hClazz = Class.forName("android.app.ActivityThread$H"); launchActivity = ShareReflectUtil.findField(hClazz, "LAUNCH_ACTIVITY").getInt(null); } catch (Throwable thr) { // Fallback to default value. launchActivity = 100; } } LAUNCH_ACTIVITY = launchActivity; } private final Context mContext; public MHMessageHandler(Context context) { while (context instanceof ContextWrapper) { final Context baseCtx = ((ContextWrapper) context).getBaseContext(); if (baseCtx == null) { break; } context = baseCtx; } mContext = context; } @Override public boolean handleMessage(Message msg) { int what = msg.what; if (what == LAUNCH_ACTIVITY) { try { final Object activityClientRecord = msg.obj; if (activityClientRecord == null) { ShareTinkerLog.w(TAG, "msg: [" + msg.what + "] has no 'obj' value."); return false; } final Field intentField = ShareReflectUtil.findField(activityClientRecord, "intent"); final Intent maybeHackedIntent = (Intent) intentField.get(activityClientRecord); if (maybeHackedIntent == null) { ShareTinkerLog.w(TAG, "cannot fetch intent from message received by mH."); return false; } ShareIntentUtil.fixIntentClassLoader(maybeHackedIntent, mContext.getClassLoader()); final ComponentName oldComponent = maybeHackedIntent.getParcelableExtra(EnvConsts.INTENT_EXTRA_OLD_COMPONENT); if (oldComponent == null) { ShareTinkerLog.w(TAG, "oldComponent was null, start " + maybeHackedIntent.getComponent() + " next."); return false; } final Field activityInfoField = ShareReflectUtil.findField(activityClientRecord, "activityInfo"); final ActivityInfo aInfo = (ActivityInfo) activityInfoField.get(activityClientRecord); if (aInfo == null) { return false; } final ActivityInfo targetAInfo = IncrementComponentManager.queryActivityInfo(oldComponent.getClassName()); if (targetAInfo == null) { ShareTinkerLog.e(TAG, "Failed to query target activity's info," + " perhaps the target is not hotpluged component. Target: " + oldComponent.getClassName()); return false; } fixActivityScreenOrientation(activityClientRecord, targetAInfo.screenOrientation); fixStubActivityInfo(aInfo, targetAInfo); maybeHackedIntent.setComponent(oldComponent); maybeHackedIntent.removeExtra(EnvConsts.INTENT_EXTRA_OLD_COMPONENT); } catch (Throwable thr) { ShareTinkerLog.e(TAG, "exception in handleMessage.", thr); } } return false; } private void fixStubActivityInfo(ActivityInfo stubAInfo, ActivityInfo targetAInfo) { copyInstanceFields(targetAInfo, stubAInfo); } private void copyInstanceFields(T srcObj, T destObj) { if (srcObj == null || destObj == null) { return; } Class infoClazz = srcObj.getClass(); while (!infoClazz.equals(Object.class)) { final Field[] fields = infoClazz.getDeclaredFields(); for (Field field : fields) { if (field.isSynthetic()) { continue; } final int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers)) { continue; } if (!field.isAccessible()) { field.setAccessible(true); } try { field.set(destObj, field.get(srcObj)); } catch (Throwable ignored) { // Ignored. } } infoClazz = infoClazz.getSuperclass(); } } private void fixActivityScreenOrientation(Object activityClientRecord, int screenOrientation) { if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; } try { final Field tokenField = ShareReflectUtil.findField(activityClientRecord, "token"); final Object token = tokenField.get(activityClientRecord); final Class activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative"); final Method getDefaultMethod = ShareReflectUtil.findMethod(activityManagerNativeClazz, "getDefault"); final Object amn = getDefaultMethod.invoke(null); final Method setRequestedOrientationMethod = ShareReflectUtil.findMethod(amn, "setRequestedOrientation", IBinder.class, int.class); setRequestedOrientationMethod.invoke(amn, token, screenOrientation); } catch (Throwable thr) { ShareTinkerLog.e(TAG, "Failed to fix screen orientation.", thr); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/handler/PMSInterceptHandler.java ================================================ package com.tencent.tinker.loader.hotplug.handler; import android.content.ComponentName; import android.content.Intent; import com.tencent.tinker.loader.hotplug.IncrementComponentManager; import com.tencent.tinker.loader.hotplug.interceptor.ServiceBinderInterceptor; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by tangyinsheng on 2017/7/31. */ public class PMSInterceptHandler implements ServiceBinderInterceptor.BinderInvocationHandler { private static final String TAG = "Tinker.PMSIntrcptHndlr"; @Override public Object invoke(Object target, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); if ("getActivityInfo".equals(methodName)) { return handleGetActivityInfo(target, method, args); } else if ("resolveIntent".equals(methodName)) { return handleResolveIntent(target, method, args); } else { return method.invoke(target, args); } } private Object handleGetActivityInfo(Object target, Method method, Object[] args) throws Throwable { final Class[] methodExceptionTypes = method.getExceptionTypes(); try { final Object res = method.invoke(target, args); if (res != null) { return res; } else { ComponentName componentName = null; int compNameIdx = 0; while (compNameIdx < args.length) { if (args[compNameIdx] instanceof ComponentName) { ShareTinkerLog.i(TAG, "locate componentName field of " + method.getName() + " done at idx: " + compNameIdx); componentName = (ComponentName) args[compNameIdx]; break; } ++compNameIdx; } if (componentName != null) { return IncrementComponentManager.queryActivityInfo(componentName.getClassName()); } else { ShareTinkerLog.w(TAG, "failed to locate componentName field of " + method.getName() + ", notice any crashes or mistakes after resolve works."); return null; } } } catch (InvocationTargetException e) { final Throwable targetThr = e.getTargetException(); if (methodExceptionTypes != null && methodExceptionTypes.length > 0) { throw (targetThr != null ? targetThr : e); } else { ShareTinkerLog.e(TAG, "unexpected exception.", (targetThr != null ? targetThr : e)); return null; } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "unexpected exception.", thr); return null; } } private Object handleResolveIntent(Object target, Method method, Object[] args) throws Throwable { final Class[] methodExceptionTypes = method.getExceptionTypes(); try { final Object res = method.invoke(target, args); if (res != null) { return res; } else { ShareTinkerLog.w(TAG, "failed to resolve activity in base package, try again in patch package."); Intent intent = null; int intentIdx = 0; while (intentIdx < args.length) { if (args[intentIdx] instanceof Intent) { ShareTinkerLog.i(TAG, "locate intent field of " + method.getName() + " done at idx: " + intentIdx); intent = (Intent) args[intentIdx]; break; } ++intentIdx; } if (intent != null) { return IncrementComponentManager.resolveIntent(intent); } else { ShareTinkerLog.w(TAG, "failed to locate intent field of " + method.getName() + ", notice any crashes or mistakes after resolve works."); return null; } } } catch (InvocationTargetException e) { final Throwable targetThr = e.getTargetException(); if (methodExceptionTypes != null && methodExceptionTypes.length > 0) { throw (targetThr != null ? targetThr : e); } else { ShareTinkerLog.e(TAG, "unexpected exception.", (targetThr != null ? targetThr : e)); return null; } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "unexpected exception.", thr); return null; } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/interceptor/HandlerMessageInterceptor.java ================================================ package com.tencent.tinker.loader.hotplug.interceptor; import android.os.Handler; import android.os.Message; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import java.lang.reflect.Field; /** * Created by tangyinsheng on 2017/7/31. */ public class HandlerMessageInterceptor extends Interceptor { private final Handler mTarget; private final MessageHandler mMessageHandler; private static Field sMCallbackField = null; static { synchronized (HandlerMessageInterceptor.class) { if (sMCallbackField == null) { try { sMCallbackField = ShareReflectUtil.findField(Handler.class, "mCallback"); } catch (Throwable ignored) { // ignored. } } } } public HandlerMessageInterceptor(Handler target, MessageHandler messageHandler) { mTarget = target; mMessageHandler = messageHandler; } @Override protected Handler.Callback fetchTarget() throws Throwable { return (Handler.Callback) sMCallbackField.get(mTarget); } @Override protected Handler.Callback decorate(final Handler.Callback callback) throws Throwable { if (callback != null && ITinkerHotplugProxy.class.isAssignableFrom(callback.getClass())) { // Already intercepted, just return the target. return callback; } else { return new CallbackWrapper(mMessageHandler, callback); } } @Override protected void inject(Handler.Callback decorated) throws Throwable { sMCallbackField.set(mTarget, decorated); } public interface MessageHandler { boolean handleMessage(Message msg); } private static class CallbackWrapper implements Handler.Callback, ITinkerHotplugProxy { private final MessageHandler mMessageHandler; private final Handler.Callback mOrigCallback; private volatile boolean mIsInHandleMethod; CallbackWrapper(MessageHandler messageHandler, Handler.Callback origCallback) { mMessageHandler = messageHandler; mOrigCallback = origCallback; mIsInHandleMethod = false; } @Override public boolean handleMessage(Message message) { boolean result = false; if (mIsInHandleMethod) { // Reentered, this may happen if origCallback calls back to us which forms a loop. return result; } else { mIsInHandleMethod = true; } if (mMessageHandler.handleMessage(message)) { result = true; } else if (mOrigCallback != null) { result = mOrigCallback.handleMessage(message); } mIsInHandleMethod = false; return result; } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/interceptor/InterceptFailedException.java ================================================ package com.tencent.tinker.loader.hotplug.interceptor; /** * Created by tangyinsheng on 2017/7/31. */ public class InterceptFailedException extends Exception { public InterceptFailedException(Throwable thr) { super(thr); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/interceptor/Interceptor.java ================================================ package com.tencent.tinker.loader.hotplug.interceptor; import android.util.Log; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Created by tangyinsheng on 2017/7/31. */ public abstract class Interceptor { private static final String TAG = "Tinker.Interceptor"; private T_TARGET mTarget = null; private volatile boolean mInstalled = false; protected abstract T_TARGET fetchTarget() throws Throwable; protected T_TARGET decorate(T_TARGET target) throws Throwable { return target; } protected abstract void inject(T_TARGET decorated) throws Throwable; public synchronized void install() throws InterceptFailedException { try { final T_TARGET target = fetchTarget(); mTarget = target; final T_TARGET decorated = decorate(target); if (decorated != target) { inject(decorated); } else { ShareTinkerLog.w(TAG, "target: " + target + " was already hooked."); } mInstalled = true; } catch (Throwable thr) { mTarget = null; throw new InterceptFailedException(thr); } } public synchronized void uninstall() throws InterceptFailedException { if (mInstalled) { try { inject(mTarget); mTarget = null; mInstalled = false; } catch (Throwable thr) { throw new InterceptFailedException(thr); } } } protected interface ITinkerHotplugProxy { // Marker interface for proxy objects created by tinker. } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/interceptor/ServiceBinderInterceptor.java ================================================ package com.tencent.tinker.loader.hotplug.interceptor; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; import android.os.IBinder; import android.os.IInterface; import android.util.Log; import com.tencent.tinker.loader.hotplug.EnvConsts; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created by tangyinsheng on 2017/7/31. */ public class ServiceBinderInterceptor extends Interceptor { private static final String TAG = "Tinker.SvcBndrIntrcptr"; private final Context mBaseContext; private final String mServiceName; private final BinderInvocationHandler mBinderInvocationHandler; private static Class sServiceManagerClazz = null; private static Field sSCacheField = null; private static Method sGetServiceMethod = null; static { synchronized (ServiceBinderInterceptor.class) { if (sServiceManagerClazz == null) { try { sServiceManagerClazz = Class.forName("android.os.ServiceManager"); sSCacheField = ShareReflectUtil.findField(sServiceManagerClazz, "sCache"); sGetServiceMethod = ShareReflectUtil.findMethod(sServiceManagerClazz, "getService", String.class); } catch (Throwable thr) { ShareTinkerLog.e(TAG, "unexpected exception.", thr); } } } } public ServiceBinderInterceptor(Context context, String serviceName, BinderInvocationHandler binderInvocationHandler) { while (context != null && context instanceof ContextWrapper) { context = ((ContextWrapper) context).getBaseContext(); } mBaseContext = context; mServiceName = serviceName; mBinderInvocationHandler = binderInvocationHandler; } @Override protected IBinder fetchTarget() throws Throwable { return (IBinder) sGetServiceMethod.invoke(null, mServiceName); } @Override protected IBinder decorate(IBinder target) throws Throwable { if (target == null) { throw new IllegalStateException("target is null."); } if (ITinkerHotplugProxy.class.isAssignableFrom(target.getClass())) { // Already intercepted, just return the target. return target; } else { return createProxy(getAllInterfacesThroughDeriveChain(target.getClass()), new FakeClientBinderHandler(target, mBinderInvocationHandler)); } } @SuppressWarnings("unchecked") @Override protected void inject(IBinder decorated) throws Throwable { final Map sCache = (Map) sSCacheField.get(null); sCache.put(mServiceName, decorated); if (Context.ACTIVITY_SERVICE.equals(mServiceName)) { fixAMSBinderCache(decorated); } else if (EnvConsts.PACKAGE_MANAGER_SRVNAME.equals(mServiceName)) { fixPMSBinderCache(mBaseContext, decorated); } } private static void fixAMSBinderCache(IBinder fakeBinder) throws Throwable { Object singletonObj = null; try { final Class amsNativeClazz = Class.forName("android.app.ActivityManagerNative"); final Field gDefaultField = ShareReflectUtil.findField(amsNativeClazz, "gDefault"); singletonObj = gDefaultField.get(null); } catch (Throwable thr) { final Class amClazz = Class.forName("android.app.ActivityManager"); final Field iActivityManagerSingletonField = ShareReflectUtil.findField(amClazz, "IActivityManagerSingleton"); singletonObj = iActivityManagerSingletonField.get(null); } final Field mInstanceField = ShareReflectUtil.findField(singletonObj, "mInstance"); final IInterface originalInterface = (IInterface) mInstanceField.get(singletonObj); if (originalInterface == null || ITinkerHotplugProxy.class.isAssignableFrom(originalInterface.getClass())) { return; } final IInterface fakeInterface = fakeBinder.queryLocalInterface(fakeBinder.getInterfaceDescriptor()); if (fakeInterface == null || !ITinkerHotplugProxy.class.isAssignableFrom(fakeInterface.getClass())) { throw new IllegalStateException("fakeBinder does not return fakeInterface, binder: " + fakeBinder + ", itf: " + fakeInterface); } mInstanceField.set(singletonObj, fakeInterface); } private static void fixPMSBinderCache(Context context, IBinder fakeBinder) throws Throwable { final Class activityThreadClazz = Class.forName("android.app.ActivityThread"); final Field sPackageManagerField = ShareReflectUtil.findField(activityThreadClazz, "sPackageManager"); final IInterface originalInterface = (IInterface) sPackageManagerField.get(null); if (originalInterface != null && !ITinkerHotplugProxy.class.isAssignableFrom(originalInterface.getClass())) { final IInterface fakeInterface = fakeBinder.queryLocalInterface(fakeBinder.getInterfaceDescriptor()); if (fakeInterface == null || !ITinkerHotplugProxy.class.isAssignableFrom(fakeInterface.getClass())) { throw new IllegalStateException("fakeBinder does not return fakeInterface, binder: " + fakeBinder + ", itf: " + fakeInterface); } sPackageManagerField.set(null, fakeInterface); } final Class applicationPackageManagerClazz = Class.forName("android.app.ApplicationPackageManager"); final Field mPMField = ShareReflectUtil.findField(applicationPackageManagerClazz, "mPM"); final PackageManager pm = context.getPackageManager(); final IInterface originalInterface2 = (IInterface) mPMField.get(pm); if (originalInterface2 != null && !ITinkerHotplugProxy.class.isAssignableFrom(originalInterface2.getClass())) { final IInterface fakeInterface = fakeBinder.queryLocalInterface(fakeBinder.getInterfaceDescriptor()); if (fakeInterface == null || !ITinkerHotplugProxy.class.isAssignableFrom(fakeInterface.getClass())) { throw new IllegalStateException("fakeBinder does not return fakeInterface, binder: " + fakeBinder + ", itf: " + fakeInterface); } mPMField.set(pm, fakeInterface); } } @SuppressWarnings("unchecked") private static T createProxy(Class[] itfs, InvocationHandler handler) { final Class[] mergedItfs = new Class[itfs.length + 1]; System.arraycopy(itfs, 0, mergedItfs, 0, itfs.length); mergedItfs[itfs.length] = ITinkerHotplugProxy.class; ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); return (T) Proxy.newProxyInstance(cl, mergedItfs, handler); } catch (Throwable thr) { final Set uniqueCls = new HashSet<>(4); for (Class itf : mergedItfs) { uniqueCls.add(itf.getClassLoader()); } if (uniqueCls.size() == 1) { cl = uniqueCls.iterator().next(); } else { cl = new ClassLoader() { @Override protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { Class res = null; for (ClassLoader cl : uniqueCls) { try { // fix some device PathClassLoader behind BootClassLoader which lead to ClassNotFoundException res = cl.loadClass(className); } catch (Throwable ignore) { } if (res != null) { return res; } } throw new ClassNotFoundException("cannot find class: " + className); } }; } try { return (T) Proxy.newProxyInstance(cl, mergedItfs, handler); } catch (Throwable thr2) { throw new RuntimeException("cl: " + cl, thr); } } } private static Class[] getAllInterfacesThroughDeriveChain(Class clazz) { if (clazz == null) { return null; } final Set> itfs = new HashSet<>(10); while (!Object.class.equals(clazz)) { itfs.addAll(Arrays.asList(clazz.getInterfaces())); clazz = clazz.getSuperclass(); } return itfs.toArray(new Class[itfs.size()]); } public interface BinderInvocationHandler { Object invoke(Object target, Method method, Object[] args) throws Throwable; } private static class FakeClientBinderHandler implements InvocationHandler { private final BinderInvocationHandler mBinderInvocationHandler; private final IBinder mOriginalClientBinder; FakeClientBinderHandler(IBinder originalClientBinder, BinderInvocationHandler binderInvocationHandler) { mOriginalClientBinder = originalClientBinder; mBinderInvocationHandler = binderInvocationHandler; } @Override public Object invoke(Object fakeClientBinder, Method method, Object[] args) throws Throwable { if ("queryLocalInterface".equals(method.getName())) { final String itfName = mOriginalClientBinder.getInterfaceDescriptor(); String stubClassName = null; if (itfName.equals("android.app.IActivityManager")) { stubClassName = "android.app.ActivityManagerNative"; } else { stubClassName = itfName + "$Stub"; } final Class stubClazz = Class.forName(stubClassName); final Method asInterfaceMethod = ShareReflectUtil.findMethod(stubClazz, "asInterface", IBinder.class); final IInterface originalInterface = (IInterface) asInterfaceMethod.invoke(null, mOriginalClientBinder); final InvocationHandler fakeInterfaceHandler = new FakeInterfaceHandler(originalInterface, (IBinder) fakeClientBinder, mBinderInvocationHandler); return createProxy(getAllInterfacesThroughDeriveChain(originalInterface.getClass()), fakeInterfaceHandler); } else { return method.invoke(mOriginalClientBinder, args); } } } private static class FakeInterfaceHandler implements InvocationHandler { private final BinderInvocationHandler mBinderInvocationHandler; private final IBinder mOriginalClientBinder; private final IInterface mOriginalInterface; FakeInterfaceHandler(IInterface originalInterface, IBinder originalClientBinder, BinderInvocationHandler binderInvocationHandler) { mOriginalInterface = originalInterface; mOriginalClientBinder = originalClientBinder; mBinderInvocationHandler = binderInvocationHandler; } @Override public Object invoke(Object fakeIInterface, Method method, Object[] args) throws Throwable { if ("asBinder".equals(method.getName())) { return mOriginalClientBinder; } else { return mBinderInvocationHandler.invoke(mOriginalInterface, method, args); } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/hotplug/interceptor/TinkerHackInstrumentation.java ================================================ package com.tencent.tinker.loader.hotplug.interceptor; import android.app.Activity; import android.app.Application; import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.hotplug.EnvConsts; import com.tencent.tinker.loader.hotplug.IncrementComponentManager; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import com.tencent.tinker.loader.shareutil.ShareReflectUtil; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; import java.lang.reflect.Field; /** * Created by tangyinsheng on 2018/3/9. */ public class TinkerHackInstrumentation extends Instrumentation { private static final String TAG = "Tinker.Instrumentation"; private final Instrumentation mOriginal; private final Object mActivityThread; private final Field mInstrumentationField; public static TinkerHackInstrumentation create(Context context) { try { final Object activityThread = ShareReflectUtil.getActivityThread(context, null); final Field mInstrumentationField = ShareReflectUtil.findField(activityThread, "mInstrumentation"); final Instrumentation original = (Instrumentation) mInstrumentationField.get(activityThread); if (original instanceof TinkerHackInstrumentation) { return (TinkerHackInstrumentation) original; } return new TinkerHackInstrumentation(original, activityThread, mInstrumentationField); } catch (Throwable thr) { throw new TinkerRuntimeException("see next stacktrace", thr); } } public void install() throws IllegalAccessException { if (mInstrumentationField.get(mActivityThread) instanceof TinkerHackInstrumentation) { ShareTinkerLog.w(TAG, "already installed, skip rest logic."); } else { mInstrumentationField.set(mActivityThread, this); } } public void uninstall() throws IllegalAccessException { mInstrumentationField.set(mActivityThread, mOriginal); } private TinkerHackInstrumentation(Instrumentation original, Object activityThread, Field instrumentationField) throws TinkerRuntimeException { mOriginal = original; mActivityThread = activityThread; mInstrumentationField = instrumentationField; try { copyAllFields(original); } catch (Throwable thr) { throw new TinkerRuntimeException(thr.getMessage(), thr); } } @Override public Activity newActivity(Class clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { processIntent(context.getClassLoader(), intent); return super.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); } @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (processIntent(cl, intent)) { return super.newActivity(cl, intent.getComponent().getClassName(), intent); } else { return super.newActivity(cl, className, intent); } } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { if (activity != null) { final ActivityInfo targetAInfo = IncrementComponentManager.queryActivityInfo(activity.getClass().getName()); if (targetAInfo != null) { fixActivityParams(activity, targetAInfo); } } super.callActivityOnCreate(activity, icicle); } @Override public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { if (activity != null) { final ActivityInfo targetAInfo = IncrementComponentManager.queryActivityInfo(activity.getClass().getName()); if (targetAInfo != null) { fixActivityParams(activity, targetAInfo); } } super.callActivityOnCreate(activity, icicle, persistentState); } @Override public void callActivityOnNewIntent(Activity activity, Intent intent) { if (activity != null) { processIntent(activity.getClass().getClassLoader(), intent); } super.callActivityOnNewIntent(activity, intent); } private boolean processIntent(ClassLoader cl, Intent intent) { if (intent == null) { return false; } ShareIntentUtil.fixIntentClassLoader(intent, cl); final ComponentName oldComponent = intent.getParcelableExtra(EnvConsts.INTENT_EXTRA_OLD_COMPONENT); if (oldComponent == null) { ShareTinkerLog.w(TAG, "oldComponent was null, start " + intent.getComponent() + " next."); return false; } final String oldComponentName = oldComponent.getClassName(); final ActivityInfo targetAInfo = IncrementComponentManager.queryActivityInfo(oldComponentName); if (targetAInfo == null) { ShareTinkerLog.e(TAG, "Failed to query target activity's info," + " perhaps the target is not hotpluged component. Target: " + oldComponentName); return false; } intent.setComponent(oldComponent); intent.removeExtra(EnvConsts.INTENT_EXTRA_OLD_COMPONENT); return true; } private void fixActivityParams(Activity target, ActivityInfo targetAInfo) { target.setRequestedOrientation(targetAInfo.screenOrientation); target.setTheme(targetAInfo.theme); try { final Field aInfoField = ShareReflectUtil.findField(target, "mActivityInfo"); aInfoField.set(target, targetAInfo); } catch (Throwable thr) { throw new TinkerRuntimeException("see next stacktrace.", thr); } } private void copyAllFields(Instrumentation src) throws IllegalAccessException { final Field[] fields = Instrumentation.class.getDeclaredFields(); for (int i = 0; i < fields.length; ++i) { fields[i].setAccessible(true); final Object value = fields[i].get(src); fields[i].set(this, value); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/Guard.java ================================================ package com.tencent.tinker.loader.shareutil; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; /** * Guard lock is a mechanism based on file lock to protect used patch files not be deleted by * cleaner. *

* Each patch version has its independent guard lock file. When a process is trying to apply a patch * version, it have to acquire a use guard (based on shared lock) of the guard lock file first, and * check the guard file content is not {@link com.tencent.tinker.loader.shareutil.Guard#CLEANING_FLAG} (its meaning is * described below). While cleaner is cleaning obsolete patch version directories, it tries to * acquire a clean guard (based on exclusive lock), If failed, which means there are other processes * using this patch version, the cleaner must skip cleaning this patch version. *

* The content of the guard lock file is empty or a byte with value * {@link com.tencent.tinker.loader.shareutil.Guard#CLEANING_FLAG}. The content is set only if the cleaner already acquired * the exclusive lock, and prepares to clean the guard lock file itself after cleaning the patch * version directory. Even if a process acquires the shared lock successfully, if the content is * {@link com.tencent.tinker.loader.shareutil.Guard#CLEANING_FLAG}, the patch version is still invalid. */ public class Guard implements Closeable { private static final int CLEANING_FLAG = 1; private static void releaseSilence(FileLock lock) { if (lock == null) { return; } try { lock.release(); } catch (IOException exception) { // ignore } } private static void closeSilence(Closeable stream) { if (stream == null) { return; } try { stream.close(); } catch (IOException ignored) { // ignored } } private final Closeable mStream; private final FileLock mLock; private Guard(Closeable stream, FileLock lock) { if (stream == null) { throw new IllegalArgumentException("stream should not be null"); } if (lock == null) { throw new IllegalArgumentException("lock should not be null"); } mStream = stream; mLock = lock; } @Override public void close() { releaseSilence(mLock); closeSilence(mStream); } /** * Try to acquire use guard, or return null if any clean guard is holding by cleaner or the * guard file marked as cleaning. * * @param guardLockFile the guard lock file * @return acquired guard, or null if failed */ public static Guard acquireUse(File guardLockFile) { final FileInputStream stream; try { stream = new FileInputStream(guardLockFile); } catch (IOException exception) { return null; } final FileLock lock; try { lock = stream.getChannel().tryLock(0, Long.MAX_VALUE, true); } catch (OverlappingFileLockException exception) { closeSilence(stream); return null; } catch (IOException exception) { closeSilence(stream); return null; } if (lock == null) { closeSilence(stream); return null; } final int flag; try { flag = stream.read(); } catch (IOException exception) { releaseSilence(lock); closeSilence(stream); return null; } if (flag == CLEANING_FLAG) { releaseSilence(lock); closeSilence(stream); return null; } return new Guard(stream, lock); } /** * Try to acquire clean guard, or return null if any use guard is holding by other process. *

* The method will mark the guard lock file as cleaning. It should only be called by cleaner. * * @param guardLockFile the guard lock file * @return acquired guard, or null if failed */ public static Guard acquireClean(File guardLockFile) { final FileOutputStream stream; try { stream = new FileOutputStream(guardLockFile); } catch (IOException exception) { return null; } final FileLock lock; try { lock = stream.getChannel().tryLock(); } catch (OverlappingFileLockException exception) { closeSilence(stream); return null; } catch (IOException exception) { closeSilence(stream); return null; } if (lock == null) { closeSilence(stream); return null; } try { stream.write(CLEANING_FLAG); } catch (IOException exception) { releaseSilence(lock); closeSilence(stream); return null; } return new Guard(stream, lock); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareArkHotDiffPatchInfo.java ================================================ /* * Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.loader.shareutil; import java.util.ArrayList; public class ShareArkHotDiffPatchInfo { public String path; public String name; public String patchMd5; public ShareArkHotDiffPatchInfo(String path, String name, String md5) { this.name = name; this.patchMd5 = md5; this.path = path; } public static void parseDiffPatchInfo(String meta, ArrayList diffList) { if (meta == null || diffList == null) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 4); if (kv == null || kv.length < 3) { continue; } final String name = kv[0].trim(); final String path = kv[1].trim(); final String md5 = kv[2].trim(); ShareArkHotDiffPatchInfo arkDiffInfo = new ShareArkHotDiffPatchInfo(path, name, md5); diffList.add(arkDiffInfo); } } public static boolean checkDiffPatchInfo(ShareArkHotDiffPatchInfo info) { if (info == null) { return false; } String name = info.name; String md5 = info.patchMd5; if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(name); sb.append(","); sb.append(path); sb.append(","); sb.append(patchMd5); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareBsDiffPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.util.ArrayList; /** * patch via bsdiff * Created by zhangshaowen on 16/3/16. */ public class ShareBsDiffPatchInfo { public String name; public String md5; public String rawCrc; public String patchMd5; public String path; public ShareBsDiffPatchInfo(String name, String md5, String path, String raw, String patch) { // TODO Auto-generated constructor stub this.name = name; this.md5 = md5; this.rawCrc = raw; this.patchMd5 = patch; this.path = path; } public static void parseDiffPatchInfo(String meta, ArrayList diffList) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 5); if (kv == null || kv.length < 5) { continue; } // key final String name = kv[0].trim(); final String path = kv[1].trim(); final String md5 = kv[2].trim(); final String rawCrc = kv[3].trim(); final String patchMd5 = kv[4].trim(); ShareBsDiffPatchInfo dexInfo = new ShareBsDiffPatchInfo(name, md5, path, rawCrc, patchMd5); diffList.add(dexInfo); } } public static boolean checkDiffPatchInfo(ShareBsDiffPatchInfo info) { if (info == null) { return false; } String name = info.name; String md5 = info.md5; if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(name); sb.append(","); sb.append(path); sb.append(","); sb.append(md5); sb.append(","); sb.append(rawCrc); sb.append(","); sb.append(patchMd5); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareConstants.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.BuildConfig; import java.util.regex.Pattern; /** * Created by zhangshaowen on 16/3/24. */ public class ShareConstants { public static final String TINKER_VERSION = BuildConfig.TINKER_VERSION; public static final int BUFFER_SIZE = 4096 * 1; public static final int MD5_LENGTH = 32; public static final int MD5_FILE_BUF_LENGTH = 4096 * 1; public static final int MAX_EXTRACT_ATTEMPTS = 2; public static final String TINKER_ID = "TINKER_ID"; public static final String NEW_TINKER_ID = "NEW_TINKER_ID"; // Please keep it synchronized with the one defined in TypedValue. public static final String PKGMETA_KEY_IS_PROTECTED_APP = "is_protected_app"; // Please keep it synchronized with the one defined in TypedValue. public static final String PKGMETA_KEY_USE_CUSTOM_FILE_PATCH = "use_custom_file_patch"; public static final String OLD_VERSION = "old"; public static final String NEW_VERSION = "new"; public static final String PATCH_BASE_NAME = "patch-"; public static final String PATCH_SUFFIX = ".apk"; public static final String PACKAGE_META_FILE = "assets/package_meta.txt"; public static final String SO_META_FILE = "assets/so_meta.txt"; public static final String SO_PATH = "lib"; public static final String DEX_META_FILE = "assets/dex_meta.txt"; public static final String DEX_PATH = "dex"; public static final String ARKHOTFIX_PATH = "arkHot"; public static final String DEFAULT_DEX_OPTIMIZE_PATH = "odex"; public static final String ANDROID_O_DEX_OPTIMIZE_PATH = "oat"; public static final String INTERPRET_DEX_OPTIMIZE_PATH = "interpet"; public static final String CHANING_DEX_OPTIMIZE_PATH = "changing"; public static final String DEX_SUFFIX = ".dex"; public static final String JAR_SUFFIX = ".jar"; public static final String APK_SUFFIX = ".apk"; public static final String ODEX_SUFFIX = ".odex"; public static final String VDEX_SUFFIX = ".vdex"; public static final String TEST_DEX_NAME = "test.dex"; public static final String CLASS_N_APK_NAME = "tinker_classN.apk"; public static final String ARKHOT_PATCH_NAME = "patch.apk"; public static final Pattern CLASS_N_PATTERN = Pattern.compile("classes(?:[2-9]?|[1-9][0-9]+)\\.dex(\\.jar)?"); public static final String CHECK_DEX_INSTALL_FAIL = "checkDexInstall failed"; public static final String CHECK_RES_INSTALL_FAIL = "checkResInstall failed"; public static final String CHECK_DEX_OAT_EXIST_FAIL = "checkDexOptExist failed"; public static final String CHECK_DEX_OAT_FORMAT_FAIL = "checkDexOptFormat failed"; // public static final String CHECK_VM_PROPERTY_FAIL = "checkVmArtProperty failed"; public static final String RES_META_FILE = "assets/res_meta.txt"; public static final String RES_ARSC = "resources.arsc"; public static final String RES_MANIFEST = "AndroidManifest.xml"; public static final String RES_TITLE = "resources_out.zip"; public static final String RES_PATH = "res"; public static final String RES_NAME = "resources.apk"; public static final String RES_ADD_TITLE = "add:"; public static final String RES_MOD_TITLE = "modify:"; public static final String RES_LARGE_MOD_TITLE = "large modify:"; public static final String RES_DEL_TITLE = "delete:"; public static final String RES_PATTERN_TITLE = "pattern:"; public static final String RES_STORE_TITLE = "store:"; public static final String ARKHOT_META_FILE = "assets/arkHot_meta.txt"; public static final String DEXMODE_RAW = "raw"; public static final String DEXMODE_JAR = "jar"; public static final String DEX_IN_JAR = "classes.dex"; public static final String PATCH_DIRECTORY_NAME = "tinker"; public static final String PATCH_DIRECTORY_NAME_SPEC = "wc_tinker_dir"; public static final String PATCH_TEMP_DIRECTORY_NAME = "tinker_temp"; public static final String PATCH_TEMP_LAST_CRASH_NAME = "tinker_last_crash"; public static final String PATCH_INFO_NAME = "patch_meta.info"; public static final String PATCH_INFO_LOCK_NAME = "info.lock"; public static final String PATCH_GUARD_DIRECTORY_NAME = "guard"; public static final String META_SUFFIX = "meta.txt"; /** * multi process share */ public static final String TINKER_SHARE_PREFERENCE_CONFIG = "tinker_share_config"; public static final String TINKER_ENABLE_CONFIG_PREFIX = "tinker_enable_"; /** * only for each process */ public static final String TINKER_OWN_PREFERENCE_CONFIG_PREFIX = "tinker_own_config_"; public static final String TINKER_SAFE_MODE_COUNT_PREFIX = "safe_mode_count_"; public static final int TINKER_SAFE_MODE_MAX_COUNT = 3; /** * notification id, use to Increasing the patch process priority * your app shouldn't use the same notification id. * if you want to define it, use {@code TinkerPatchService.setTinkerNotificationId} */ public static final int TINKER_PATCH_SERVICE_NOTIFICATION = -1119860829; //resource type public static final int TYPE_PATCH_FILE = 1; public static final int TYPE_PATCH_INFO = 2; public static final int TYPE_DEX = 3; public static final int TYPE_DEX_OPT = 4; public static final int TYPE_LIBRARY = 5; public static final int TYPE_RESOURCE = 6; public static final int TYPE_CLASS_N_DEX = 7; public static final int TYPE_ARKHOT_SO = 8; public static final int TINKER_DISABLE = 0x00; public static final int TINKER_DEX_MASK = 0x01; public static final int TINKER_NATIVE_LIBRARY_MASK = 0x02; public static final int TINKER_RESOURCE_MASK = 0x04; public static final int TINKER_ARKHOT_MASK = 0x08; public static final int TINKER_DEX_AND_LIBRARY = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_ARKHOT_MASK; public static final int TINKER_ENABLE_ALL = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_RESOURCE_MASK | TINKER_ARKHOT_MASK; //load error code public static final int ERROR_LOAD_OK = 0; public static final int ERROR_LOAD_DISABLE = -1; public static final int ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST = -2; public static final int ERROR_LOAD_PATCH_INFO_NOT_EXIST = -3; public static final int ERROR_LOAD_PATCH_INFO_CORRUPTED = -4; public static final int ERROR_LOAD_PATCH_INFO_BLANK = -5; public static final int ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST = -6; public static final int ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST = -7; public static final int ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL = -8; public static final int ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST = -9; public static final int ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST = -10; public static final int ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST = -11; public static final int ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL = -12; public static final int ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH = -13; public static final int ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION = -14; public static final int ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION = -15; public static final int ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION = -16; public static final int ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST = -17; public static final int ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST = -18; public static final int ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL = -19; public static final int ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION = -20; //resource public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST = -21; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST = -22; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION = -23; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH = -24; public static final int ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION = -25; // -26 & -27 is used by WeChat internal logic. public static final int ERROR_LOAD_PATCH_BAIL_HACK_FAILURE = -28; public static final int ERROR_LOAD_PATCH_GUARD_FILE_NOT_EXIST = -29; public static final int ERROR_LOAD_GET_INTENT_FAIL = -10000; //load exception code //recover error code public static final int ERROR_LOAD_EXCEPTION_UNKNOWN = -1; public static final int ERROR_LOAD_EXCEPTION_DEX = -2; public static final int ERROR_LOAD_EXCEPTION_RESOURCE = -3; public static final int ERROR_LOAD_EXCEPTION_UNCAUGHT = -4; public static final int ERROR_LOAD_EXCEPTION_COMPONENT_HOTPLUG = -5; //patch listener error code public static final int ERROR_PATCH_OK = 0; public static final int ERROR_PATCH_DISABLE = -1; public static final int ERROR_PATCH_NOTEXIST = -2; public static final int ERROR_PATCH_RUNNING = -3; public static final int ERROR_PATCH_INSERVICE = -4; public static final int ERROR_PATCH_JIT = -5; public static final int ERROR_PATCH_ALREADY_APPLY = -6; public static final int ERROR_PATCH_RETRY_COUNT_LIMIT = -7; //package check error code public static final int ERROR_PACKAGE_CHECK_OK = 0; public static final int ERROR_PACKAGE_CHECK_SIGNATURE_FAIL = -1; public static final int ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = -2; public static final int ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED = -3; public static final int ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED = -4; public static final int ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = -5; public static final int ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = -6; public static final int ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = -7; public static final int ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED = -8; public static final int ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = -9; // interpret error type public static final int TYPE_INTERPRET_OK = 0; public static final int TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR = 1; public static final int TYPE_INTERPRET_COMMAND_ERROR = 2; } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareDexDiffPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.TinkerRuntimeException; import java.util.ArrayList; /** * Created by zhangshaowen on 16/4/11. */ public class ShareDexDiffPatchInfo { public final String rawName; public final String destMd5InDvm; public final String destMd5InArt; public final String oldDexCrC; public final String newOrPatchedDexCrC; public final String dexDiffMd5; public final String path; public final String dexMode; public final boolean isJarMode; /** * if it is jar mode, and the name is end of .dex, we should repackage it with zip, with renaming name.dex.jar */ public final String realName; public ShareDexDiffPatchInfo(String name, String path, String destMd5InDvm, String destMd5InArt, String dexDiffMd5, String oldDexCrc, String newOrPatchedDexCrC, String dexMode) { // TODO Auto-generated constructor stub this.rawName = name; this.path = path; this.destMd5InDvm = destMd5InDvm; this.destMd5InArt = destMd5InArt; this.dexDiffMd5 = dexDiffMd5; this.oldDexCrC = oldDexCrc; this.newOrPatchedDexCrC = newOrPatchedDexCrC; this.dexMode = dexMode; if (dexMode.equals(ShareConstants.DEXMODE_JAR)) { this.isJarMode = true; if (SharePatchFileUtil.isRawDexFile(name)) { realName = name + ShareConstants.JAR_SUFFIX; } else { realName = name; } } else if (dexMode.equals(ShareConstants.DEXMODE_RAW)) { this.isJarMode = false; this.realName = name; } else { throw new TinkerRuntimeException("can't recognize dex mode:" + dexMode); } } public static void parseDexDiffPatchInfo(String meta, ArrayList dexList) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 8); if (kv == null || kv.length < 8) { continue; } // key final String name = kv[0].trim(); final String path = kv[1].trim(); final String destMd5InDvm = kv[2].trim(); final String destMd5InArt = kv[3].trim(); final String dexDiffMd5 = kv[4].trim(); final String oldDexCrc = kv[5].trim(); final String newDexCrc = kv[6].trim(); final String dexMode = kv[7].trim(); ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt, dexDiffMd5, oldDexCrc, newDexCrc, dexMode); dexList.add(dexInfo); } } public static boolean checkDexDiffPatchInfo(ShareDexDiffPatchInfo info) { if (info == null) { return false; } String name = info.rawName; String md5 = (ShareTinkerInternals.isVmArt() ? info.destMd5InArt : info.destMd5InDvm); if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(rawName); sb.append(","); sb.append(path); sb.append(","); sb.append(destMd5InDvm); sb.append(","); sb.append(destMd5InArt); sb.append(","); sb.append(oldDexCrC); sb.append(","); sb.append(newOrPatchedDexCrC); sb.append(","); sb.append(dexDiffMd5); sb.append(","); sb.append(dexMode); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * Created by tangyinsheng on 2017/3/13. */ public class ShareElfFile implements Closeable { public static final int FILE_TYPE_OTHERS = -1; public static final int FILE_TYPE_ODEX = 0; public static final int FILE_TYPE_ELF = 1; private final FileInputStream fis; private final Map sectionNameToHeaderMap = new HashMap<>(); public ElfHeader elfHeader = null; public ProgramHeader[] programHeaders = null; public SectionHeader[] sectionHeaders = null; public ShareElfFile(File file) throws IOException { fis = new FileInputStream(file); final FileChannel channel = fis.getChannel(); elfHeader = new ElfHeader(channel); final ByteBuffer headerBuffer = ByteBuffer.allocate(128); headerBuffer.limit(elfHeader.ePhEntSize); headerBuffer.order(elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); channel.position(elfHeader.ePhOff); programHeaders = new ProgramHeader[elfHeader.ePhNum]; for (int i = 0; i < programHeaders.length; ++i) { readUntilLimit(channel, headerBuffer, "failed to read phdr."); programHeaders[i] = new ProgramHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]); } channel.position(elfHeader.eShOff); headerBuffer.limit(elfHeader.eShEntSize); sectionHeaders = new SectionHeader[elfHeader.eShNum]; for (int i = 0; i < sectionHeaders.length; ++i) { readUntilLimit(channel, headerBuffer, "failed to read shdr."); sectionHeaders[i] = new SectionHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]); } if (elfHeader.eShStrNdx > 0) { final SectionHeader shStrTabSectionHeader = sectionHeaders[elfHeader.eShStrNdx]; final ByteBuffer shStrTab = getSection(shStrTabSectionHeader); for (SectionHeader shdr : sectionHeaders) { shStrTab.position(shdr.shName); shdr.shNameStr = readCString(shStrTab); sectionNameToHeaderMap.put(shdr.shNameStr, shdr); } } } private static void assertInRange(int b, int lb, int ub, String errMsg) throws IOException { if (b < lb || b > ub) { throw new IOException(errMsg); } } public static int getFileTypeByMagic(File file) throws IOException { InputStream is = null; try { final byte[] magicBuf = new byte[4]; is = new FileInputStream(file); is.read(magicBuf); if (magicBuf[0] == 'd' && magicBuf[1] == 'e' && magicBuf[2] == 'y' && magicBuf[3] == '\n') { return FILE_TYPE_ODEX; } else if (magicBuf[0] == 0x7F && magicBuf[1] == 'E' && magicBuf[2] == 'L' && magicBuf[3] == 'F') { return FILE_TYPE_ELF; } else { return FILE_TYPE_OTHERS; } } finally { if (is != null) { try { is.close(); } catch (Throwable thr) { // Ignored. } } } } public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException { bufferOut.rewind(); int bytesRead = channel.read(bufferOut); if (bytesRead != bufferOut.limit()) { throw new IOException(errMsg + " Rest bytes insufficient, expect to read " + bufferOut.limit() + " bytes but only " + bytesRead + " bytes were read."); } bufferOut.flip(); } public static String readCString(ByteBuffer buffer) { final byte[] rawBuffer = buffer.array(); int begin = buffer.position(); while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) { buffer.position(buffer.position() + 1); } // Move to the start of next cstring. buffer.position(buffer.position() + 1); return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName("ASCII")); } public FileChannel getChannel() { return fis.getChannel(); } public boolean is32BitElf() { return (elfHeader.eIndent[ElfHeader.EI_CLASS] == ElfHeader.ELFCLASS32); } public ByteOrder getDataOrder() { return (elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); } public SectionHeader getSectionHeaderByName(String name) { return sectionNameToHeaderMap.get(name); } public ByteBuffer getSection(SectionHeader sectionHeader) throws IOException { final ByteBuffer result = ByteBuffer.allocate((int) sectionHeader.shSize); fis.getChannel().position(sectionHeader.shOffset); readUntilLimit(fis.getChannel(), result, "failed to read section: " + sectionHeader.shNameStr); return result; } public ByteBuffer getSegment(ProgramHeader programHeader) throws IOException { final ByteBuffer result = ByteBuffer.allocate((int) programHeader.pFileSize); fis.getChannel().position(programHeader.pOffset); readUntilLimit(fis.getChannel(), result, "failed to read segment (type: " + programHeader.pType + ")."); return result; } @Override public void close() throws IOException { fis.close(); sectionNameToHeaderMap.clear(); programHeaders = null; sectionHeaders = null; } public static class ElfHeader { // Elf indent field index. public static final int EI_CLASS = 4; public static final int EI_DATA = 5; public static final int EI_VERSION = 6; // Elf classes. public static final int ELFCLASS32 = 1; public static final int ELFCLASS64 = 2; // Elf data encoding. public static final int ELFDATA2LSB = 1; public static final int ELFDATA2MSB = 2; // Elf types. public static final int ET_NONE = 0; public static final int ET_REL = 1; public static final int ET_EXEC = 2; public static final int ET_DYN = 3; public static final int ET_CORE = 4; public static final int ET_LOPROC = 0xff00; public static final int ET_HIPROC = 0xffff; // Elf indent version. public static final int EV_CURRENT = 1; private static final int EI_NINDENT = 16; public final byte[] eIndent = new byte[EI_NINDENT]; public final short eType; public final short eMachine; public final int eVersion; public final long eEntry; public final long ePhOff; public final long eShOff; public final int eFlags; public final short eEhSize; public final short ePhEntSize; public final short ePhNum; public final short eShEntSize; public final short eShNum; public final short eShStrNdx; private ElfHeader(FileChannel channel) throws IOException { channel.position(0); channel.read(ByteBuffer.wrap(eIndent)); if (eIndent[0] != 0x7F || eIndent[1] != 'E' || eIndent[2] != 'L' || eIndent[3] != 'F') { throw new IOException(String.format("bad elf magic: %x %x %x %x.", eIndent[0], eIndent[1], eIndent[2], eIndent[3])); } assertInRange(eIndent[EI_CLASS], ELFCLASS32, ELFCLASS64, "bad elf class: " + eIndent[EI_CLASS]); assertInRange(eIndent[EI_DATA], ELFDATA2LSB, ELFDATA2MSB, "bad elf data encoding: " + eIndent[EI_DATA]); final ByteBuffer restBuffer = ByteBuffer.allocate(eIndent[EI_CLASS] == ELFCLASS32 ? 36 : 48); restBuffer.order(eIndent[EI_DATA] == ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); readUntilLimit(channel, restBuffer, "failed to read rest part of ehdr."); eType = restBuffer.getShort(); eMachine = restBuffer.getShort(); eVersion = restBuffer.getInt(); assertInRange(eVersion, EV_CURRENT, EV_CURRENT, "bad elf version: " + eVersion); switch (eIndent[EI_CLASS]) { case ELFCLASS32: eEntry = restBuffer.getInt(); ePhOff = restBuffer.getInt(); eShOff = restBuffer.getInt(); break; case ELFCLASS64: eEntry = restBuffer.getLong(); ePhOff = restBuffer.getLong(); eShOff = restBuffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + eIndent[EI_CLASS]); } eFlags = restBuffer.getInt(); eEhSize = restBuffer.getShort(); ePhEntSize = restBuffer.getShort(); ePhNum = restBuffer.getShort(); eShEntSize = restBuffer.getShort(); eShNum = restBuffer.getShort(); eShStrNdx = restBuffer.getShort(); } } public static class ProgramHeader { // Segment types. public static final int PT_NULL = 0; public static final int PT_LOAD = 1; public static final int PT_DYNAMIC = 2; public static final int PT_INTERP = 3; public static final int PT_NOTE = 4; public static final int PT_SHLIB = 5; public static final int PT_PHDR = 6; public static final int PT_LOPROC = 0x70000000; public static final int PT_HIPROC = 0x7fffffff; // Segment flags. public static final int PF_R = 0x04; public static final int PF_W = 0x02; public static final int PF_X = 0x01; public final int pType; public final int pFlags; public final long pOffset; public final long pVddr; public final long pPddr; public final long pFileSize; public final long pMemSize; public final long pAlign; private ProgramHeader(ByteBuffer buffer, int elfClass) throws IOException { switch (elfClass) { case ElfHeader.ELFCLASS32: pType = buffer.getInt(); pOffset = buffer.getInt(); pVddr = buffer.getInt(); pPddr = buffer.getInt(); pFileSize = buffer.getInt(); pMemSize = buffer.getInt(); pFlags = buffer.getInt(); pAlign = buffer.getInt(); break; case ElfHeader.ELFCLASS64: pType = buffer.getInt(); pFlags = buffer.getInt(); pOffset = buffer.getLong(); pVddr = buffer.getLong(); pPddr = buffer.getLong(); pFileSize = buffer.getLong(); pMemSize = buffer.getLong(); pAlign = buffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + elfClass); } } } public static class SectionHeader { // Special section indexes. public static final int SHN_UNDEF = 0; public static final int SHN_LORESERVE = 0xff00; public static final int SHN_LOPROC = 0xff00; public static final int SHN_HIPROC = 0xff1f; public static final int SHN_ABS = 0xfff1; public static final int SHN_COMMON = 0xfff2; public static final int SHN_HIRESERVE = 0xffff; // Section types. public static final int SHT_NULL = 0; public static final int SHT_PROGBITS = 1; public static final int SHT_SYMTAB = 2; public static final int SHT_STRTAB = 3; public static final int SHT_RELA = 4; public static final int SHT_HASH = 5; public static final int SHT_DYNAMIC = 6; public static final int SHT_NOTE = 7; public static final int SHT_NOBITS = 8; public static final int SHT_REL = 9; public static final int SHT_SHLIB = 10; public static final int SHT_DYNSYM = 11; public static final int SHT_LOPROC = 0x70000000; public static final int SHT_HIPROC = 0x7fffffff; public static final int SHT_LOUSER = 0x80000000; public static final int SHT_HIUSER = 0xffffffff; // Section flags. public static final int SHF_WRITE = 0x1; public static final int SHF_ALLOC = 0x2; public static final int SHF_EXECINSTR = 0x4; public static final int SHF_MASKPROC = 0xf0000000; public final int shName; public final int shType; public final long shFlags; public final long shAddr; public final long shOffset; public final long shSize; public final int shLink; public final int shInfo; public final long shAddrAlign; public final long shEntSize; public String shNameStr; private SectionHeader(ByteBuffer buffer, int elfClass) throws IOException { switch (elfClass) { case ElfHeader.ELFCLASS32: shName = buffer.getInt(); shType = buffer.getInt(); shFlags = buffer.getInt(); shAddr = buffer.getInt(); shOffset = buffer.getInt(); shSize = buffer.getInt(); shLink = buffer.getInt(); shInfo = buffer.getInt(); shAddrAlign = buffer.getInt(); shEntSize = buffer.getInt(); break; case ElfHeader.ELFCLASS64: shName = buffer.getInt(); shType = buffer.getInt(); shFlags = buffer.getLong(); shAddr = buffer.getLong(); shOffset = buffer.getLong(); shSize = buffer.getLong(); shLink = buffer.getInt(); shInfo = buffer.getInt(); shAddrAlign = buffer.getLong(); shEntSize = buffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + elfClass); } shNameStr = null; } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareFileLockHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileLock; /** * Created by zhangshaowen on 16/6/3. */ public class ShareFileLockHelper implements Closeable { public static final int MAX_LOCK_ATTEMPTS = 3; public static final int LOCK_WAIT_EACH_TIME = 10; private static final String TAG = "Tinker.FileLockHelper"; private final FileOutputStream outputStream; private final FileLock fileLock; private ShareFileLockHelper(File lockFile) throws IOException { outputStream = new FileOutputStream(lockFile); int numAttempts = 0; boolean isGetLockSuccess; FileLock localFileLock = null; //just wait twice, Exception saveException = null; while (numAttempts < MAX_LOCK_ATTEMPTS) { numAttempts++; try { localFileLock = outputStream.getChannel().lock(); isGetLockSuccess = (localFileLock != null); if (isGetLockSuccess) { break; } } catch (Exception e) { saveException = e; ShareTinkerLog.e(TAG, "getInfoLock Thread failed time:" + LOCK_WAIT_EACH_TIME); } //it can just sleep 0, afraid of cpu scheduling try { Thread.sleep(LOCK_WAIT_EACH_TIME); } catch (Exception ignore) { ShareTinkerLog.e(TAG, "getInfoLock Thread sleep exception", ignore); } } if (localFileLock == null) { throw new IOException("Tinker Exception:FileLockHelper lock file failed: " + lockFile.getAbsolutePath(), saveException); } fileLock = localFileLock; } public static ShareFileLockHelper getFileLock(File lockFile) throws IOException { return new ShareFileLockHelper(lockFile); } @Override public void close() throws IOException { try { if (fileLock != null) { fileLock.release(); } } finally { if (outputStream != null) { outputStream.close(); } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareIntentUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.content.Intent; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; /** * Created by zhangshaowen on 16/3/18. */ public class ShareIntentUtil { //intent public static final String INTENT_RETURN_CODE = "intent_return_code"; public static final String INTENT_PATCH_OLD_VERSION = "intent_patch_old_version"; public static final String INTENT_PATCH_NEW_VERSION = "intent_patch_new_version"; public static final String INTENT_IS_PROTECTED_APP = "intent_is_protected_app"; public static final String INTENT_USE_CUSTOM_PATCH = "intent_use_custom_patch"; public static final String INTENT_PATCH_MISMATCH_DEX_PATH = "intent_patch_mismatch_dex_path"; public static final String INTENT_PATCH_MISSING_DEX_PATH = "intent_patch_missing_dex_path"; public static final String INTENT_PATCH_DEXES_PATH = "intent_patch_dexes_path"; public static final String INTENT_PATCH_MISMATCH_LIB_PATH = "intent_patch_mismatch_lib_path"; public static final String INTENT_PATCH_MISSING_LIB_PATH = "intent_patch_missing_lib_path"; public static final String INTENT_PATCH_LIBS_PATH = "intent_patch_libs_path"; public static final String INTENT_PATCH_COST_TIME = "intent_patch_cost_time"; public static final String INTENT_PATCH_EXCEPTION = "intent_patch_exception"; public static final String INTENT_PATCH_PACKAGE_PATCH_CHECK = "intent_patch_package_patch_check"; public static final String INTENT_PATCH_PACKAGE_CONFIG = "intent_patch_package_config"; public static final String INTENT_PATCH_SYSTEM_OTA = "intent_patch_system_ota"; public static final String INTENT_PATCH_OAT_DIR = "intent_patch_oat_dir"; public static final String INTENT_PATCH_INTERPRET_EXCEPTION = "intent_patch_interpret_exception"; private static final String TAG = "ShareIntentUtil"; public static void setIntentReturnCode(Intent intent, int code) { intent.putExtra(INTENT_RETURN_CODE, code); } public static int getIntentReturnCode(Intent intent) { return getIntExtra(intent, INTENT_RETURN_CODE, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); } public static void setIntentPatchCostTime(Intent intent, long cost) { intent.putExtra(INTENT_PATCH_COST_TIME, cost); } public static long getIntentPatchCostTime(Intent intent) { return intent.getLongExtra(INTENT_PATCH_COST_TIME, 0); } public static Throwable getIntentPatchException(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_EXCEPTION); if (serializable != null) { return (Throwable) serializable; } return null; } public static Throwable getIntentInterpretException(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_INTERPRET_EXCEPTION); if (serializable != null) { return (Throwable) serializable; } return null; } public static HashMap getIntentPatchDexPaths(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_DEXES_PATH); if (serializable != null) { return (HashMap) serializable; } return null; } public static HashMap getIntentPatchLibsPaths(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_LIBS_PATH); if (serializable != null) { return (HashMap) serializable; } return null; } public static HashMap getIntentPackageConfig(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_PACKAGE_CONFIG); if (serializable != null) { return (HashMap) serializable; } return null; } public static ArrayList getStringArrayListExtra(Intent intent, String name) { if (null == intent) { return null; } ArrayList ret = null; try { ret = intent.getStringArrayListExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getStringExtra exception:" + e.getMessage()); ret = null; } return ret; } public static String getStringExtra(Intent intent, String name) { if (null == intent) { return null; } String ret = null; try { ret = intent.getStringExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getStringExtra exception:" + e.getMessage()); ret = null; } return ret; } public static Serializable getSerializableExtra(Intent intent, String name) { if (null == intent) { return null; } Serializable ret = null; try { ret = intent.getSerializableExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getSerializableExtra exception:" + e.getMessage()); ret = null; } return ret; } public static int getIntExtra(Intent intent, String name, int defaultValue) { if (null == intent) { return defaultValue; } int ret = defaultValue; try { ret = intent.getIntExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getIntExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static boolean getBooleanExtra(Intent intent, String name, boolean defaultValue) { if (null == intent) { return defaultValue; } boolean ret = defaultValue; try { ret = intent.getBooleanExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getBooleanExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static long getLongExtra(Intent intent, String name, long defaultValue) { if (null == intent) { return defaultValue; } long ret = defaultValue; try { ret = intent.getLongExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getIntExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static void fixIntentClassLoader(Intent intent, ClassLoader cl) { try { intent.setExtrasClassLoader(cl); } catch (Throwable thr) { thr.printStackTrace(); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; /** * Created by tangyinsheng on 2017/3/14. */ public final class ShareOatUtil { private static final String TAG = "Tinker.OatUtil"; private ShareOatUtil() { throw new UnsupportedOperationException(); } /** * Get instruction set used to generate {@code oatFile}. * * @param oatFile * the oat file. * @return * the instruction used to generate this oat file, if the oat file does not * contain this value, an empty string will be returned. * * @throws IOException * If anything wrong when parsing the elf format or locating target field in oat header. */ public static String getOatFileInstructionSet(File oatFile) throws Throwable { ShareElfFile elfFile = null; String result = ""; try { elfFile = new ShareElfFile(oatFile); final ShareElfFile.SectionHeader roDataHdr = elfFile.getSectionHeaderByName(".rodata"); if (roDataHdr == null) { throw new IOException("Unable to find .rodata section."); } final FileChannel channel = elfFile.getChannel(); channel.position(roDataHdr.shOffset); final byte[] oatMagicAndVersion = new byte[8]; ShareElfFile.readUntilLimit(channel, ByteBuffer.wrap(oatMagicAndVersion), "Failed to read oat magic and version."); if (oatMagicAndVersion[0] != 'o' || oatMagicAndVersion[1] != 'a' || oatMagicAndVersion[2] != 't' || oatMagicAndVersion[3] != '\n') { throw new IOException( String.format("Bad oat magic: %x %x %x %x", oatMagicAndVersion[0], oatMagicAndVersion[1], oatMagicAndVersion[2], oatMagicAndVersion[3]) ); } final int versionOffsetFromOatBegin = 4; final int versionBytes = 3; final String oatVersion = new String(oatMagicAndVersion, versionOffsetFromOatBegin, versionBytes, Charset.forName("ASCII")); try { Integer.parseInt(oatVersion); } catch (NumberFormatException e) { throw new IOException("Bad oat version: " + oatVersion); } ByteBuffer buffer = ByteBuffer.allocate(128); buffer.order(elfFile.getDataOrder()); // TODO This is a risk point, since each oat version may use a different offset. // So far it's ok. Perhaps we should use oatVersionNum to judge the right offset in // the future. final int isaNumOffsetFromOatBegin = 12; channel.position(roDataHdr.shOffset + isaNumOffsetFromOatBegin); buffer.limit(4); ShareElfFile.readUntilLimit(channel, buffer, "Failed to read isa num."); int isaNum = buffer.getInt(); if (isaNum < 0 || isaNum >= InstructionSet.values().length) { throw new IOException("Bad isa num: " + isaNum); } switch (InstructionSet.values()[isaNum]) { case kArm: case kThumb2: result = "arm"; break; case kArm64: result = "arm64"; break; case kX86: result = "x86"; break; case kX86_64: result = "x86_64"; break; case kMips: result = "mips"; break; case kMips64: result = "mips64"; break; case kNone: result = "none"; break; default: throw new IOException("Should not reach here."); } } finally { if (elfFile != null) { try { elfFile.close(); } catch (Exception ignored) { // Ignored. } } } return result; } private enum InstructionSet { kNone, kArm, kArm64, kThumb2, kX86, kX86_64, kMips, kMips64 } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchFileUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.MessageDigest; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class SharePatchFileUtil { private static final String TAG = "Tinker.PatchFileUtil"; private static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * data dir, such as /data/data/tinker.sample.android/tinker * @param context * @return */ public static File getPatchDirectory(Context context) { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. return null; } final String dirName = ("oppo".equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT == 22) ? ShareConstants.PATCH_DIRECTORY_NAME_SPEC : ShareConstants.PATCH_DIRECTORY_NAME; return new File(applicationInfo.dataDir, dirName); } public static File getPatchTempDirectory(Context context) { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. return null; } return new File(applicationInfo.dataDir, ShareConstants.PATCH_TEMP_DIRECTORY_NAME); } public static File getPatchLastCrashFile(Context context) { File tempFile = getPatchTempDirectory(context); if (tempFile == null) { return null; } return new File(tempFile, ShareConstants.PATCH_TEMP_LAST_CRASH_NAME); } public static File getPatchInfoFile(String patchDirectory) { return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_NAME); } public static File getPatchInfoLockFile(String patchDirectory) { return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_LOCK_NAME); } public static String getPatchVersionDirectory(String version) { if (version == null || version.length() != ShareConstants.MD5_LENGTH) { return null; } return ShareConstants.PATCH_BASE_NAME + version.substring(0, 8); } public static String getPatchVersionFile(String version) { if (version == null || version.length() != ShareConstants.MD5_LENGTH) { return null; } return getPatchVersionDirectory(version) + ShareConstants.PATCH_SUFFIX; } public static File getGuardDirectory(String patchDirectory) { return new File(patchDirectory, ShareConstants.PATCH_GUARD_DIRECTORY_NAME); } public static boolean checkIfMd5Valid(final String object) { if ((object == null) || (object.length() != ShareConstants.MD5_LENGTH)) { return false; } return true; } public static String checkTinkerLastUncaughtCrash(Context context) { File crashFile = SharePatchFileUtil.getPatchLastCrashFile(context); if (!SharePatchFileUtil.isLegalFile(crashFile)) { return null; } StringBuffer buffer = new StringBuffer(); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(crashFile))); String line; while ((line = in.readLine()) != null) { buffer.append(line); buffer.append("\n"); } } catch (Exception e) { ShareTinkerLog.e(TAG, "checkTinkerLastUncaughtCrash exception: " + e); return null; } finally { closeQuietly(in); } return buffer.toString(); } /** * Closes the given {@code obj}. Suppresses any exceptions. */ @SuppressLint("NewApi") public static void closeQuietly(Object obj) { if (obj == null) return; if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (Throwable ignored) { // Ignored. } } else if (Build.VERSION.SDK_INT >= 19 && obj instanceof AutoCloseable) { try { ((AutoCloseable) obj).close(); } catch (Throwable ignored) { // Ignored. } } else if (obj instanceof ZipFile) { try { ((ZipFile) obj).close(); } catch (Throwable ignored) { // Ignored. } } else { throw new IllegalArgumentException("obj: " + obj + " cannot be closed."); } } public static final boolean isLegalFile(File file) { return file != null && file.exists() && file.canRead() && file.isFile() && file.length() > 0; } /** * For some special device whose dex2oat procedure is optimized for tinker. (e.g. vivo, oppo) * * Because these devices by-pass our dex2oat request, which cause vm to load tinker's dex with interpret-mode * and generate nothing instead of a valid oat file. It's fine to skip the check so far. * * @param file * @return */ public static final boolean shouldAcceptEvenIfIllegal(File file) { final boolean isSpecialManufacturer = "vivo".equalsIgnoreCase(Build.MANUFACTURER) || "oppo".equalsIgnoreCase(Build.MANUFACTURER) || "meizu".equalsIgnoreCase(Build.MANUFACTURER); final boolean isSpecialOSVer = ShareTinkerInternals.isNewerOrEqualThanVersion(29, true) || (ShareTinkerInternals.isArkHotRuning()); final boolean isFileIllegal = !file.exists() || file.length() == 0; return (isSpecialManufacturer || isSpecialOSVer) && isFileIllegal; } /** * get directory size * * @param directory * @return */ public static long getFileOrDirectorySize(File directory) { if (directory == null || !directory.exists()) { return 0; } if (directory.isFile()) { return directory.length(); } long totalSize = 0; File[] fileList = directory.listFiles(); if (fileList != null) { for (File file : fileList) { if (file.isDirectory()) { totalSize = totalSize + getFileOrDirectorySize(file); } else { totalSize = totalSize + file.length(); } } } return totalSize; } public static final boolean safeDeleteFile(File file) { if (file == null) { return true; } if (file.exists()) { ShareTinkerLog.i(TAG, "safeDeleteFile, try to delete path: " + file.getPath()); boolean deleted = file.delete(); if (!deleted) { ShareTinkerLog.e(TAG, "Failed to delete file, try to delete when exit. path: " + file.getPath()); file.deleteOnExit(); } return deleted; } return true; } public static final boolean deleteDir(String dir) { if (dir == null) { return false; } return deleteDir(new File(dir)); } public static final boolean deleteDir(File file) { if (file == null || (!file.exists())) { return false; } if (file.isFile()) { safeDeleteFile(file); } else if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { for (File subFile : files) { deleteDir(subFile); } safeDeleteFile(file); } } return true; } public static void deleteDirAsync(String path) { deleteDirAsync(new File(path)); } public static void deleteDirAsync(File file) { new Thread(new Runnable() { @Override public void run() { SharePatchFileUtil.deleteDir(file); } }, "tinker-clean") { { setPriority(Thread.NORM_PRIORITY - 1); } }.start(); } /** * Returns whether the file is a valid file. */ public static boolean verifyFileMd5(File file, String md5) { if (md5 == null) { return false; } String fileMd5 = getMD5(file); if (fileMd5 == null) { return false; } return md5.equals(fileMd5); } public static boolean isRawDexFile(String fileName) { if (fileName == null) { return false; } return fileName.endsWith(ShareConstants.DEX_SUFFIX); } /** * Returns whether the dex file is a valid file. * dex may wrap with jar */ public static boolean verifyDexFileMd5(File file, String md5) { return verifyDexFileMd5(file, ShareConstants.DEX_IN_JAR, md5); } public static boolean verifyDexFileMd5(File file, String entryName, String md5) { if (file == null || md5 == null || entryName == null) { return false; } //if it is not the raw dex, we check the stream instead String fileMd5 = ""; if (isRawDexFile(file.getName())) { fileMd5 = getMD5(file); } else { ZipFile dexJar = null; try { dexJar = new ZipFile(file); ZipEntry classesDex = dexJar.getEntry(entryName); // no code if (null == classesDex) { ShareTinkerLog.e(TAG, "There's no entry named: " + ShareConstants.DEX_IN_JAR + " in " + file.getAbsolutePath()); return false; } InputStream is = null; try { is = dexJar.getInputStream(classesDex); fileMd5 = getMD5(is); } catch (Throwable e) { ShareTinkerLog.e(TAG, "exception occurred when get md5: " + file.getAbsolutePath(), e); } finally { closeQuietly(is); } } catch (Throwable e) { ShareTinkerLog.e(TAG, "Bad dex jar file: " + file.getAbsolutePath(), e); return false; } finally { closeZip(dexJar); } } return md5.equals(fileMd5); } public static void copyFileUsingStream(File source, File dest) throws IOException { if (!SharePatchFileUtil.isLegalFile(source) || dest == null) { return; } if (source.getAbsolutePath().equals(dest.getAbsolutePath())) { return; } FileInputStream is = null; FileOutputStream os = null; File parent = dest.getParentFile(); if (parent != null && (!parent.exists())) { parent.mkdirs(); } try { is = new FileInputStream(source); os = new FileOutputStream(dest, false); byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; int length; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } } finally { closeQuietly(is); closeQuietly(os); } } /** * for faster, read and get the contents * * @throws IOException */ public static String loadDigestes(JarFile jarFile, JarEntry je) throws Exception { InputStream bis = null; StringBuilder sb = new StringBuilder(); try { InputStream is = jarFile.getInputStream(je); byte[] bytes = new byte[ShareConstants.BUFFER_SIZE]; bis = new BufferedInputStream(is); int readBytes; while ((readBytes = bis.read(bytes)) > 0) { sb.append(new String(bytes, 0, readBytes)); } } finally { closeQuietly(bis); } return sb.toString(); } /** * Get the md5 for inputStream. * This method cost less memory. It read bufLen bytes from the FileInputStream once. * * @param is */ public final static String getMD5(final InputStream is) { if (is == null) { return null; } try { BufferedInputStream bis = new BufferedInputStream(is); MessageDigest md = MessageDigest.getInstance("MD5"); StringBuilder md5Str = new StringBuilder(32); byte[] buf = new byte[ShareConstants.MD5_FILE_BUF_LENGTH]; int readCount; while ((readCount = bis.read(buf)) != -1) { md.update(buf, 0, readCount); } byte[] hashValue = md.digest(); for (int i = 0; i < hashValue.length; i++) { md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); } return md5Str.toString(); } catch (Exception e) { return null; } } public static String getMD5(byte[] buffer) { try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(buffer); byte[] md = mdTemp.digest(); int j = md.length; char[] str = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { return null; } } /** * Get the md5 for the file. call getMD5(FileInputStream is, int bufLen) inside. * * @param file */ public static String getMD5(final File file) { if (file == null || !file.exists()) { return null; } FileInputStream fin = null; try { fin = new FileInputStream(file); String md5 = getMD5(fin); return md5; } catch (Exception e) { ShareTinkerLog.e(TAG, e.getMessage()); return null; } finally { closeQuietly(fin); } } /** * change the jar file path as the makeDexElements do * Android O change its path * * @param path * @param optimizedDirectory * @return */ public static String optimizedPathFor(File path, File optimizedDirectory) { if (ShareTinkerInternals.isAfterAndroidO()) { // dex_location = /foo/bar/baz.jar // odex_location = /foo/bar/oat//baz.odex String currentInstructionSet; try { currentInstructionSet = ShareTinkerInternals.getCurrentInstructionSet(); } catch (Exception e) { throw new TinkerRuntimeException("getCurrentInstructionSet fail:", e); } File parentFile = path.getParentFile(); String fileName = path.getName(); int index = fileName.lastIndexOf('.'); if (index > 0) { fileName = fileName.substring(0, index); } String result = parentFile.getAbsolutePath() + "/oat/" + currentInstructionSet + "/" + fileName + ShareConstants.ODEX_SUFFIX; return result; } String fileName = path.getName(); if (!fileName.endsWith(ShareConstants.DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += ShareConstants.DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(ShareConstants.DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath(); } public static void closeZip(ZipFile zipFile) { try { if (zipFile != null) { zipFile.close(); } } catch (IOException e) { ShareTinkerLog.w(TAG, "Failed to close resource", e); } } public static boolean checkResourceArscMd5(File resOutput, String destMd5) { ZipFile resourceZip = null; try { resourceZip = new ZipFile(resOutput); ZipEntry arscEntry = resourceZip.getEntry(ShareConstants.RES_ARSC); if (arscEntry == null) { ShareTinkerLog.i(TAG, "checkResourceArscMd5 resources.arsc not found"); return false; } InputStream inputStream = null; try { inputStream = resourceZip.getInputStream(arscEntry); String md5 = SharePatchFileUtil.getMD5(inputStream); if (md5 != null && md5.equals(destMd5)) { return true; } } finally { closeQuietly(inputStream); } } catch (Throwable e) { ShareTinkerLog.i(TAG, "checkResourceArscMd5 throwable:" + e.getMessage()); } finally { SharePatchFileUtil.closeZip(resourceZip); } return false; } public static void ensureFileDirectory(File file) { if (file == null) { return; } File parentFile = file.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.os.Build; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; /** * Created by zhangshaowen on 16/3/16. */ public class SharePatchInfo { private static final String TAG = "Tinker.PatchInfo"; public static final int MAX_EXTRACT_ATTEMPTS = ShareConstants.MAX_EXTRACT_ATTEMPTS; public static final String OLD_VERSION = ShareConstants.OLD_VERSION; public static final String NEW_VERSION = ShareConstants.NEW_VERSION; public static final String IS_PROTECTED_APP = ShareConstants.PKGMETA_KEY_IS_PROTECTED_APP; public static final String USE_CUSTOM_FILE_PATCH = ShareConstants.PKGMETA_KEY_USE_CUSTOM_FILE_PATCH; public static final String VERSION_TO_REMOVE = "version_to_remove"; public static final String FINGER_PRINT = "print"; public static final String OAT_DIR = "dir"; public static final String IS_REMOVE_INTERPRET_OAT_DIR = "is_remove_interpret_oat_dir"; public static final String DEFAULT_DIR = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; /* * The meaning of this field has changed after moving cleaning task to patch process. * Before: Version of previous patch. * After: Version of patch which active main process is used. */ public String oldVersion; public String newVersion; public boolean isProtectedApp; public boolean useCustomPatch; /** * @deprecated Patch version directories cleaning is moved from main process to patch process, * this should always be an empty string. */ @Deprecated public String versionToRemove; public String fingerPrint; public String oatDir; public boolean isRemoveInterpretOATDir; public SharePatchInfo(String oldVer, String newVer, boolean isProtectedApp, boolean useCustomPatch, String versionToRemove, String finger, String oatDir, boolean isRemoveInterpretOATDir) { // TODO Auto-generated constructor stub this.oldVersion = oldVer; this.newVersion = newVer; this.isProtectedApp = isProtectedApp; this.versionToRemove = versionToRemove; this.fingerPrint = finger; this.oatDir = oatDir; this.isRemoveInterpretOATDir = isRemoveInterpretOATDir; } public static SharePatchInfo readAndCheckPropertyWithLock(File pathInfoFile, File lockFile) { if (pathInfoFile == null || lockFile == null) { return null; } File lockParentFile = lockFile.getParentFile(); if (!lockParentFile.exists()) { lockParentFile.mkdirs(); } SharePatchInfo patchInfo; ShareFileLockHelper fileLock = null; try { fileLock = ShareFileLockHelper.getFileLock(lockFile); patchInfo = readAndCheckProperty(pathInfoFile); } catch (Exception e) { throw new TinkerRuntimeException("readAndCheckPropertyWithLock fail", e); } finally { try { if (fileLock != null) { fileLock.close(); } } catch (IOException e) { ShareTinkerLog.w(TAG, "releaseInfoLock error", e); } } return patchInfo; } public static boolean rewritePatchInfoFileWithLock(File pathInfoFile, SharePatchInfo info, File lockFile) { if (pathInfoFile == null || info == null || lockFile == null) { return false; } File lockParentFile = lockFile.getParentFile(); if (!lockParentFile.exists()) { lockParentFile.mkdirs(); } boolean rewriteSuccess; ShareFileLockHelper fileLock = null; try { fileLock = ShareFileLockHelper.getFileLock(lockFile); rewriteSuccess = rewritePatchInfoFile(pathInfoFile, info); } catch (Exception e) { throw new TinkerRuntimeException("rewritePatchInfoFileWithLock fail", e); } finally { try { if (fileLock != null) { fileLock.close(); } } catch (IOException e) { ShareTinkerLog.i(TAG, "releaseInfoLock error", e); } } return rewriteSuccess; } private static SharePatchInfo readAndCheckProperty(File pathInfoFile) { boolean isReadPatchSuccessful = false; int numAttempts = 0; String oldVer = null; String newVer = null; String lastFingerPrint = null; boolean isProtectedApp = false; boolean useCustomPatch = false; String versionToRemove = null; String oatDir = null; boolean isRemoveInterpretOATDir = false; while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isReadPatchSuccessful) { numAttempts++; Properties properties = new Properties(); FileInputStream inputStream = null; try { inputStream = new FileInputStream(pathInfoFile); properties.load(inputStream); oldVer = properties.getProperty(OLD_VERSION); newVer = properties.getProperty(NEW_VERSION); final String isProtectedAppStr = properties.getProperty(IS_PROTECTED_APP); isProtectedApp = (isProtectedAppStr != null && !isProtectedAppStr.isEmpty() && !"0".equals(isProtectedAppStr)); final String useCustomPatchStr = properties.getProperty(USE_CUSTOM_FILE_PATCH); useCustomPatch = (useCustomPatchStr != null && !useCustomPatchStr.isEmpty() && !"0".equals(useCustomPatchStr)); versionToRemove = properties.getProperty(VERSION_TO_REMOVE); lastFingerPrint = properties.getProperty(FINGER_PRINT); oatDir = properties.getProperty(OAT_DIR); final String isRemoveInterpretOATDirStr = properties.getProperty(IS_REMOVE_INTERPRET_OAT_DIR); isRemoveInterpretOATDir = (isRemoveInterpretOATDirStr != null && !isRemoveInterpretOATDirStr.isEmpty() && !"0".equals(isRemoveInterpretOATDirStr)); } catch (IOException e) { ShareTinkerLog.w(TAG, "read property failed, e:" + e); } finally { SharePatchFileUtil.closeQuietly(inputStream); } if (oldVer == null || newVer == null) { continue; } //oldVer may be "" or 32 md5 if ((!oldVer.equals("") && !SharePatchFileUtil.checkIfMd5Valid(oldVer)) || !SharePatchFileUtil.checkIfMd5Valid(newVer)) { ShareTinkerLog.w(TAG, "path info file corrupted:" + pathInfoFile.getAbsolutePath()); continue; } else { isReadPatchSuccessful = true; } } if (isReadPatchSuccessful) { return new SharePatchInfo(oldVer, newVer, isProtectedApp, useCustomPatch, versionToRemove, lastFingerPrint, oatDir, isRemoveInterpretOATDir); } return null; } private static boolean rewritePatchInfoFile(File pathInfoFile, SharePatchInfo info) { if (pathInfoFile == null || info == null) { return false; } // write fingerprint if it is null or nil if (ShareTinkerInternals.isNullOrNil(info.fingerPrint)) { info.fingerPrint = Build.FINGERPRINT; } if (ShareTinkerInternals.isNullOrNil(info.oatDir)) { info.oatDir = DEFAULT_DIR; } ShareTinkerLog.i(TAG, "rewritePatchInfoFile file path:" + pathInfoFile.getAbsolutePath() + " , oldVer:" + info.oldVersion + ", newVer:" + info.newVersion + ", isProtectedApp:" + (info.isProtectedApp ? 1 : 0) + ", versionToRemove:" + info.versionToRemove + ", fingerprint:" + info.fingerPrint + ", oatDir:" + info.oatDir + ", isRemoveInterpretOATDir:" + (info.isRemoveInterpretOATDir ? 1 : 0) + ", stack: " + android.util.Log.getStackTraceString(new Throwable()) ); boolean isWritePatchSuccessful = false; int numAttempts = 0; File parentFile = pathInfoFile.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isWritePatchSuccessful) { numAttempts++; Properties newProperties = new Properties(); newProperties.put(OLD_VERSION, info.oldVersion); newProperties.put(NEW_VERSION, info.newVersion); newProperties.put(IS_PROTECTED_APP, (info.isProtectedApp ? "1" : "0")); newProperties.put(USE_CUSTOM_FILE_PATCH, (info.useCustomPatch ? "1" : "0")); newProperties.put(VERSION_TO_REMOVE, info.versionToRemove); newProperties.put(FINGER_PRINT, info.fingerPrint); newProperties.put(OAT_DIR, info.oatDir); newProperties.put(IS_REMOVE_INTERPRET_OAT_DIR, (info.isRemoveInterpretOATDir ? "1" : "0")); FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(pathInfoFile, false); String comment = "from old version:" + info.oldVersion + " to new version:" + info.newVersion; newProperties.store(outputStream, comment); } catch (Exception e) { ShareTinkerLog.w(TAG, "write property failed, e:" + e); } finally { SharePatchFileUtil.closeQuietly(outputStream); } SharePatchInfo tempInfo = readAndCheckProperty(pathInfoFile); isWritePatchSuccessful = tempInfo != null && tempInfo.oldVersion.equals(info.oldVersion) && tempInfo.newVersion.equals(info.newVersion); if (!isWritePatchSuccessful) { pathInfoFile.delete(); } } return isWritePatchSuccessful; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareReflectUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.content.Context; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; /** * Created by zhangshaowen on 16/8/22. */ public class ShareReflectUtil { /** * Locates a given field anywhere in the class inheritance hierarchy. * * @param instance an object to search the field into. * @param name field name * @return a field object * @throws NoSuchFieldException if the field cannot be located */ public static Field findField(Object instance, String name) throws NoSuchFieldException { for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); } public static Field findField(Class originClazz, String name) throws NoSuchFieldException { for (Class clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + originClazz); } /** * Locates a given method anywhere in the class inheritance hierarchy. * * @param instance an object to search the method into. * @param name method name * @param parameterTypes method parameter types * @return a method object * @throws NoSuchMethodException if the method cannot be located */ public static Method findMethod(Object instance, String name, Class... parameterTypes) throws NoSuchMethodException { for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); } /** * Locates a given method anywhere in the class inheritance hierarchy. * * @param clazz a class to search the method into. * @param name method name * @param parameterTypes method parameter types * @return a method object * @throws NoSuchMethodException if the method cannot be located */ public static Method findMethod(Class clazz, String name, Class... parameterTypes) throws NoSuchMethodException { for (; clazz != null; clazz = clazz.getSuperclass()) { try { Method method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + clazz); } /** * Locates a given constructor anywhere in the class inheritance hierarchy. * * @param instance an object to search the constructor into. * @param parameterTypes constructor parameter types * @return a constructor object * @throws NoSuchMethodException if the constructor cannot be located */ public static Constructor findConstructor(Object instance, Class... parameterTypes) throws NoSuchMethodException { return findConstructor(instance.getClass(), parameterTypes); } /** * Locates a given constructor anywhere in the class inheritance hierarchy. * * @param clazz an class to search the constructor into. * @param parameterTypes constructor parameter types * @return a constructor object * @throws NoSuchMethodException if the constructor cannot be located */ public static Constructor findConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException { for (Class currClazz = clazz; currClazz != null; currClazz = currClazz.getSuperclass()) { try { Constructor ctor = currClazz.getDeclaredConstructor(parameterTypes); if (!ctor.isAccessible()) { ctor.setAccessible(true); } return ctor; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Constructor" + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + clazz); } /** * Replace the value of a field containing a non null array, by a new array containing the * elements of the original array plus the elements of extraElements. * * @param instance the instance whose field is to be modified. * @param fieldName the field to modify. * @param extraElements elements to append at the end of the array. */ public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) jlrField.get(instance); Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length); // NOTE: changed to copy extraElements first, for patch load first System.arraycopy(extraElements, 0, combined, 0, extraElements.length); System.arraycopy(original, 0, combined, extraElements.length, original.length); jlrField.set(instance, combined); } /** * Replace the value of a field containing a non null array, by a new array containing the * elements of the original array plus the elements of extraElements. * * @param instance the instance whose field is to be modified. * @param fieldName the field to modify. */ public static void reduceFieldArray(Object instance, String fieldName, int reduceSize) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { if (reduceSize <= 0) { return; } Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) jlrField.get(instance); int finalLength = original.length - reduceSize; if (finalLength <= 0) { return; } Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength); System.arraycopy(original, reduceSize, combined, 0, finalLength); jlrField.set(instance, combined); } public static Object getActivityThread(Context context, Class activityThread) { try { if (activityThread == null) { activityThread = Class.forName("android.app.ActivityThread"); } Method m = activityThread.getMethod("currentActivityThread"); m.setAccessible(true); Object currentActivityThread = m.invoke(null); if (currentActivityThread == null && context != null) { // In older versions of Android (prior to frameworks/base 66a017b63461a22842) // the currentActivityThread was built on thread locals, so we'll need to try // even harder Field mLoadedApk = context.getClass().getField("mLoadedApk"); mLoadedApk.setAccessible(true); Object apk = mLoadedApk.get(context); Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread"); mActivityThreadField.setAccessible(true); currentActivityThread = mActivityThreadField.get(apk); } return currentActivityThread; } catch (Throwable ignore) { return null; } } /** * Handy method for fetching hidden integer constant value in system classes. * * @param clazz * @param fieldName * @return */ public static int getValueOfStaticIntField(Class clazz, String fieldName, int defVal) { try { final Field field = findField(clazz, fieldName); return field.getInt(null); } catch (Throwable thr) { return defVal; } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareResPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.regex.Pattern; /** * Created by zhangshaowen on 16/8/9. */ public class ShareResPatchInfo { public String arscBaseCrc = null; public String resArscMd5 = null; public ArrayList addRes = new ArrayList<>(); public ArrayList deleteRes = new ArrayList<>(); public ArrayList modRes = new ArrayList<>(); public HashMap storeRes = new HashMap<>(); //use linkHashMap instead? public ArrayList largeModRes = new ArrayList<>(); public HashMap largeModMap = new HashMap<>(); public HashSet patterns = new HashSet<>(); public static void parseAllResPatchInfo(String meta, ShareResPatchInfo info) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (int i = 0; i < lines.length; i++) { String line = lines[i]; if (line == null || line.length() <= 0) { continue; } if (line.startsWith(ShareConstants.RES_TITLE)) { final String[] kv = line.split(",", 3); info.arscBaseCrc = kv[1]; info.resArscMd5 = kv[2]; } else if (line.startsWith(ShareConstants.RES_PATTERN_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.patterns.add(convertToPatternString(lines[i + 1])); i++; } } else if (line.startsWith(ShareConstants.RES_ADD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.addRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_MOD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.modRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_LARGE_MOD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { String nextLine = lines[i + 1]; final String[] data = nextLine.split(",", 3); String name = data[0]; LargeModeInfo largeModeInfo = new LargeModeInfo(); largeModeInfo.md5 = data[1]; largeModeInfo.crc = Long.parseLong(data[2]); info.largeModRes.add(name); info.largeModMap.put(name, largeModeInfo); i++; } } else if (line.startsWith(ShareConstants.RES_DEL_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.deleteRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_STORE_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.storeRes.put(lines[i + 1], null); i++; } } } } public static boolean checkFileInPattern(HashSet patterns, String key) { if (!patterns.isEmpty()) { for (Iterator it = patterns.iterator(); it.hasNext();) { Pattern p = it.next(); if (p.matcher(key).matches()) { return true; } } } return false; } public static boolean checkResPatchInfo(ShareResPatchInfo info) { if (info == null) { return false; } String md5 = info.resArscMd5; if (md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } private static Pattern convertToPatternString(String input) { //convert \\. if (input.contains(".")) { input = input.replaceAll("\\.", "\\\\."); } //convert ?to . if (input.contains("?")) { input = input.replaceAll("\\?", "\\."); } //convert * to.* if (input.contains("*")) { input = input.replace("*", ".*"); } Pattern pattern = Pattern.compile(input); return pattern; } public static void parseResPatchInfoFirstLine(String meta, ShareResPatchInfo info) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); String firstLine = lines[0]; if (firstLine == null || firstLine.length() <= 0) { throw new TinkerRuntimeException("res meta Corrupted:" + meta); } final String[] kv = firstLine.split(",", 3); info.arscBaseCrc = kv[1]; info.resArscMd5 = kv[2]; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("resArscMd5:" + resArscMd5 + "\n"); sb.append("arscBaseCrc:" + arscBaseCrc + "\n"); for (Pattern pattern : patterns) { sb.append("pattern:" + pattern + "\n"); } for (String add : addRes) { sb.append("addedSet:" + add + "\n"); } for (String mod : modRes) { sb.append("modifiedSet:" + mod + "\n"); } for (String largeMod : largeModRes) { sb.append("largeModifiedSet:" + largeMod + "\n"); } for (String del : deleteRes) { sb.append("deletedSet:" + del + "\n"); } for (String store : storeRes.keySet()) { sb.append("storeSet:" + store + "\n"); } return sb.toString(); } public static class LargeModeInfo { public String md5 = null; public long crc; public File file = null; } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareSecurityCheck.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.HashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Created by zhangshaowen on 16/3/10. */ public class ShareSecurityCheck { private static final String TAG = "Tinker.SecurityCheck"; /** * static to faster * public key */ private static String mPublicKeyMd5 = null; private final Context mContext; private final HashMap metaContentMap; private final HashMap packageProperties; public ShareSecurityCheck(Context context) { mContext = context; metaContentMap = new HashMap<>(); packageProperties = new HashMap<>(); if (mPublicKeyMd5 == null) { init(mContext); } } public HashMap getMetaContentMap() { return metaContentMap; } /** * Nullable * * @return HashMap */ public HashMap getPackagePropertiesIfPresent() { if (!packageProperties.isEmpty()) { return packageProperties; } String property = metaContentMap.get(ShareConstants.PACKAGE_META_FILE); if (property == null) { return null; } String[] lines = property.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } //it is comment if (line.startsWith("#")) { continue; } final String[] kv = line.split("=", 2); if (kv == null || kv.length < 2) { continue; } packageProperties.put(kv[0].trim(), kv[1].trim()); } return packageProperties; } public boolean verifyPatchMetaSignature(File path) { if (!SharePatchFileUtil.isLegalFile(path)) { return false; } JarFile jarFile = null; try { jarFile = new JarFile(path); final Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); // no code if (jarEntry == null) { continue; } final String name = jarEntry.getName(); if (name.startsWith("META-INF/")) { continue; } //for faster, only check the meta.txt files //we will check other files's md5 written in meta files if (!name.endsWith(ShareConstants.META_SUFFIX)) { continue; } metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry)); Certificate[] certs = jarEntry.getCertificates(); if (certs == null || !check(path, certs)) { return false; } } } catch (Exception e) { throw new TinkerRuntimeException( String.format("ShareSecurityCheck file %s, size %d verifyPatchMetaSignature fail", path.getAbsolutePath(), path.length()), e); } finally { try { if (jarFile != null) { jarFile.close(); } } catch (IOException e) { ShareTinkerLog.e(TAG, path.getAbsolutePath(), e); } } return true; } // verify the signature of the Apk private boolean check(File path, Certificate[] certs) { if (certs.length > 0) { for (int i = certs.length - 1; i >= 0; i--) { try { if (mPublicKeyMd5.equals(SharePatchFileUtil.getMD5(certs[i].getEncoded()))) { return true; } } catch (Exception e) { ShareTinkerLog.e(TAG, path.getAbsolutePath(), e); } } } return false; } @SuppressLint("PackageManagerGetSignatures") private void init(Context context) { ByteArrayInputStream stream = null; try { PackageManager pm = context.getPackageManager(); String packageName = context.getPackageName(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); mPublicKeyMd5 = SharePatchFileUtil.getMD5(packageInfo.signatures[0].toByteArray()); if (mPublicKeyMd5 == null) { throw new TinkerRuntimeException("get public key md5 is null"); } } catch (Exception e) { throw new TinkerRuntimeException("ShareSecurityCheck init public key fail", e); } finally { SharePatchFileUtil.closeQuietly(stream); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerInternals.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Build.VERSION; import android.text.TextUtils; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 16/3/10. */ public class ShareTinkerInternals { private static final String TAG = "Tinker.TinkerInternals"; private static final boolean VM_IS_ART = isVmArt(System.getProperty("java.vm.version")); private static final boolean VM_IS_JIT = isVmJitInternal(); private static final String PATCH_PROCESS_NAME = ":patch"; private static Boolean isPatchProcess = null; private static Boolean isARKHotRunning = null; /** * or you may just hardcode them in your app */ private static final String[] processName = {null}; private static String tinkerID = null; private static String currentInstructionSet = null; public static boolean isVmArt() { return VM_IS_ART || Build.VERSION.SDK_INT >= 21; } public static boolean isVmJit() { return VM_IS_JIT && Build.VERSION.SDK_INT < 24; } public static boolean isArkHotRuning() { if (isARKHotRunning != null) { return isARKHotRunning; } isARKHotRunning = false; Class arkApplicationInfo = null; try { arkApplicationInfo = ClassLoader.getSystemClassLoader() .getParent().loadClass("com.huawei.ark.app.ArkApplicationInfo"); Method isRunningInArkHot = null; isRunningInArkHot = arkApplicationInfo.getDeclaredMethod("isRunningInArk"); isRunningInArkHot.setAccessible(true); isARKHotRunning = (Boolean) isRunningInArkHot.invoke(null); } catch (ClassNotFoundException e) { ShareTinkerLog.i(TAG, "class not found exception"); } catch (NoSuchMethodException e) { ShareTinkerLog.i(TAG, "no such method exception"); } catch (SecurityException e) { ShareTinkerLog.i(TAG, "security exception"); } catch (IllegalAccessException e) { ShareTinkerLog.i(TAG, "illegal access exception"); } catch (InvocationTargetException e) { ShareTinkerLog.i(TAG, "invocation target exception"); } catch (IllegalArgumentException e) { ShareTinkerLog.i(TAG, "illegal argument exception"); } return isARKHotRunning; } public static boolean isAfterAndroidO() { return Build.VERSION.SDK_INT > 25; } public static String getCurrentInstructionSet() { if (currentInstructionSet != null) { return currentInstructionSet; } try { Class clazz = Class.forName("dalvik.system.VMRuntime"); Method currentGet = clazz.getDeclaredMethod("getCurrentInstructionSet"); currentGet.setAccessible(true); currentInstructionSet = (String) currentGet.invoke(null); } catch (Throwable ignored) { switch (Build.CPU_ABI) { case "armeabi": case "armeabi-v7a": currentInstructionSet = "arm"; break; case "arm64-v8a": currentInstructionSet = "arm64"; break; case "x86": currentInstructionSet = "x86"; break; case "x86_64": currentInstructionSet = "x86_64"; break; case "mips": currentInstructionSet = "mips"; break; case "mips64": currentInstructionSet = "mips64"; break; default: throw new IllegalStateException("Unsupported abi: " + Build.CPU_ABI); } } ShareTinkerLog.d(TAG, "getCurrentInstructionSet:" + currentInstructionSet); return currentInstructionSet; } public static boolean is32BitEnv() { final String currISA = getCurrentInstructionSet(); return "arm".equals(currISA) || "x86".equals(currISA) || "mips".equals(currISA); } public static boolean isSystemOTA(String lastFingerPrint) { String currentFingerprint = Build.FINGERPRINT; if (lastFingerPrint == null || lastFingerPrint.equals("") || currentFingerprint == null || currentFingerprint.equals("")) { ShareTinkerLog.d(TAG, "fingerprint empty:" + lastFingerPrint + ",current:" + currentFingerprint); return false; } else { if (lastFingerPrint.equals(currentFingerprint)) { ShareTinkerLog.d(TAG, "same fingerprint:" + currentFingerprint); return false; } else { ShareTinkerLog.d(TAG, "system OTA,fingerprint not equal:" + lastFingerPrint + "," + currentFingerprint); return true; } } } public static ShareDexDiffPatchInfo changeTestDexToClassN(ShareDexDiffPatchInfo rawDexInfo, int index) { if (rawDexInfo.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) { String newName; if (index != 1) { newName = "classes" + index + ".dex"; } else { newName = "classes.dex"; } return new ShareDexDiffPatchInfo(newName, rawDexInfo.path, rawDexInfo.destMd5InDvm, rawDexInfo.destMd5InArt, rawDexInfo.dexDiffMd5, rawDexInfo.oldDexCrC, rawDexInfo.newOrPatchedDexCrC, rawDexInfo.dexMode); } return null; } public static boolean isNullOrNil(final String object) { if ((object == null) || (object.length() <= 0)) { return true; } return false; } /** * thinker package check * * @param context * @param tinkerFlag * @param patchFile * @param securityCheck * @return */ public static int checkTinkerPackage(Context context, int tinkerFlag, File patchFile, ShareSecurityCheck securityCheck) { int returnCode = checkSignatureAndTinkerID(context, patchFile, securityCheck); if (returnCode == ShareConstants.ERROR_PACKAGE_CHECK_OK) { returnCode = checkPackageAndTinkerFlag(securityCheck, tinkerFlag); } return returnCode; } /** * check patch file signature and TINKER_ID * * @param context * @param patchFile * @param securityCheck * @return */ public static int checkSignatureAndTinkerID(Context context, File patchFile, ShareSecurityCheck securityCheck) { if (!securityCheck.verifyPatchMetaSignature(patchFile)) { return ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL; } String oldTinkerId = getManifestTinkerID(context); if (oldTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND; } HashMap properties = securityCheck.getPackagePropertiesIfPresent(); if (properties == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND; } String patchTinkerId = properties.get(ShareConstants.TINKER_ID); if (patchTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND; } if (!oldTinkerId.equals(patchTinkerId)) { ShareTinkerLog.e(TAG, "tinkerId in patch is not matched with the one in base pack, base: %s, patch: %s.", oldTinkerId, patchTinkerId); return ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } public static int checkPackageAndTinkerFlag(ShareSecurityCheck securityCheck, int tinkerFlag) { if (isTinkerEnabledAll(tinkerFlag)) { return ShareConstants.ERROR_PACKAGE_CHECK_OK; } HashMap metaContentMap = securityCheck.getMetaContentMap(); //check dex boolean dexEnable = isTinkerEnabledForDex(tinkerFlag); if (!dexEnable && metaContentMap.containsKey(ShareConstants.DEX_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check native library boolean nativeEnable = isTinkerEnabledForNativeLib(tinkerFlag); if (!nativeEnable && metaContentMap.containsKey(ShareConstants.SO_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check resource boolean resEnable = isTinkerEnabledForResource(tinkerFlag); if (!resEnable && metaContentMap.containsKey(ShareConstants.RES_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } /** * not like {@cod ShareSecurityCheck.getPackagePropertiesIfPresent} * we don't check Signatures or other files, we just get the package meta's properties directly * * @param patchFile * @return */ public static Properties fastGetPatchPackageMeta(File patchFile) { if (patchFile == null || !patchFile.isFile() || patchFile.length() == 0) { ShareTinkerLog.e(TAG, "patchFile is illegal"); return null; } ZipFile zipFile = null; try { zipFile = new ZipFile(patchFile); ZipEntry packageEntry = zipFile.getEntry(ShareConstants.PACKAGE_META_FILE); if (packageEntry == null) { ShareTinkerLog.e(TAG, "patch meta entry not found"); return null; } InputStream inputStream = null; try { inputStream = zipFile.getInputStream(packageEntry); Properties properties = new Properties(); properties.load(inputStream); return properties; } finally { SharePatchFileUtil.closeQuietly(inputStream); } } catch (IOException e) { ShareTinkerLog.e(TAG, "fastGetPatchPackageMeta exception:" + e.getMessage()); return null; } finally { SharePatchFileUtil.closeZip(zipFile); } } public static String getManifestTinkerID(Context context) { if (tinkerID != null) { return tinkerID; } try { ApplicationInfo appInfo = context.getPackageManager() .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); Object object = appInfo.metaData.get(ShareConstants.TINKER_ID); if (object != null) { tinkerID = String.valueOf(object); } else { tinkerID = null; } } catch (Exception e) { ShareTinkerLog.e(TAG, "getManifestTinkerID exception:" + e.getMessage()); return null; } return tinkerID; } public static boolean isTinkerEnabledForDex(int flag) { return (flag & ShareConstants.TINKER_DEX_MASK) != 0; } public static boolean isTinkerEnabledForNativeLib(int flag) { return (flag & ShareConstants.TINKER_NATIVE_LIBRARY_MASK) != 0; } public static boolean isTinkerEnabledForResource(int flag) { //FIXME:res flag depends dex flag return (flag & ShareConstants.TINKER_RESOURCE_MASK) != 0; } public static boolean isTinkerEnabledForArkHot(int flag) { return (flag & ShareConstants.TINKER_ARKHOT_MASK) != 0; } public static String getTypeString(int type) { switch (type) { case ShareConstants.TYPE_DEX: return "dex"; case ShareConstants.TYPE_DEX_OPT: return "dex_opt"; case ShareConstants.TYPE_LIBRARY: return "lib"; case ShareConstants.TYPE_PATCH_FILE: return "patch_file"; case ShareConstants.TYPE_PATCH_INFO: return "patch_info"; case ShareConstants.TYPE_RESOURCE: return "resource"; default: return "unknown"; } } /** * you can set Tinker disable in runtime at some times! * * @param context */ public static void setTinkerDisableWithSharedPreferences(Context context) { SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); String keyName = getTinkerSwitchSPKey(context); sp.edit().putBoolean(keyName, false).commit(); } /** * can't load or receive any patch! * * @param context * @return */ public static boolean isTinkerEnableWithSharedPreferences(Context context) { if (context == null) { return false; } SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); String keyName = getTinkerSwitchSPKey(context); return sp.getBoolean(keyName, true); } private static String getTinkerSwitchSPKey(Context context) { String tmpTinkerId = getManifestTinkerID(context); if (isNullOrNil(tmpTinkerId)) { tmpTinkerId = "@@"; } return ShareConstants.TINKER_ENABLE_CONFIG_PREFIX + ShareConstants.TINKER_VERSION + "_" + tmpTinkerId; } private static final String SAFEMODE_COUNT_REC_PREFIX = "safemode_count_rec_"; public static int getSafeModeCount(Context context) { final String processName = ShareTinkerInternals.getProcessName(context); final String recFileName = SAFEMODE_COUNT_REC_PREFIX + processName; final File safeModeRecFile = new File(SharePatchFileUtil.getPatchDirectory(context), recFileName); DataInputStream dis = null; try { dis = new DataInputStream(new FileInputStream(safeModeRecFile)); final String expectedKey = ShareConstants.TINKER_SAFE_MODE_COUNT_PREFIX + ShareConstants.TINKER_VERSION; final String actualKey = dis.readUTF(); if (!expectedKey.equals(actualKey)) { ShareTinkerLog.w(TAG, "getSafeModeCount: key is not equal, expt: %s, actul: %s, return 0 instead.", expectedKey, actualKey); return 0; } final int count = dis.readInt(); ShareTinkerLog.i(TAG, "getSafeModeCount: count: %s", count); return count; } catch (Throwable ignored) { ShareTinkerLog.w(TAG, "getSafeModeCount: recFileName:" + recFileName + " failed, return 0 instead."); return 0; } finally { SharePatchFileUtil.closeQuietly(dis); } } public static void setSafeModeCount(Context context, int count) { final String processName = ShareTinkerInternals.getProcessName(context); final String recFileName = SAFEMODE_COUNT_REC_PREFIX + processName; final File safeModeRecFile = new File(SharePatchFileUtil.getPatchDirectory(context), recFileName); if (!safeModeRecFile.exists()) { safeModeRecFile.getParentFile().mkdirs(); } DataOutputStream dos = null; try { dos = new DataOutputStream(new FileOutputStream(safeModeRecFile)); final String key = ShareConstants.TINKER_SAFE_MODE_COUNT_PREFIX + ShareConstants.TINKER_VERSION; dos.writeUTF(key); dos.writeInt(count); ShareTinkerLog.i(TAG, "setSafeModeCount: count: %s", count); } catch (Throwable ignored) { ShareTinkerLog.w(TAG, "setSafeModeCount: recFileName:" + recFileName + " failed, return 0 instead."); } finally { SharePatchFileUtil.closeQuietly(dos); } } public static boolean isTinkerEnabled(int flag) { return (flag != ShareConstants.TINKER_DISABLE); } public static boolean isTinkerEnabledAll(int flag) { return (flag == ShareConstants.TINKER_ENABLE_ALL); } public static boolean isInMainProcess(Context context) { String mainProcessName = null; ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo != null) { mainProcessName = applicationInfo.processName; } if (isNullOrNil(mainProcessName)) { mainProcessName = context.getPackageName(); } String processName = getProcessName(context); if (processName == null || processName.length() == 0) { processName = ""; } return mainProcessName.equals(processName); } public static boolean isInPatchProcess(Context context) { if (isPatchProcess != null) { return isPatchProcess; } isPatchProcess = getProcessName(context).endsWith(PATCH_PROCESS_NAME); return isPatchProcess; } public static String getCurrentOatMode(Context context, String current) { if (current.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH)) { if (isInMainProcess(context)) { current = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; } else { current = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH; } } return current; } public static void killAllOtherProcess(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List appProcessList = am .getRunningAppProcesses(); if (appProcessList == null) { return; } // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M // BUT THAT'S ENOUGH HERE for (ActivityManager.RunningAppProcessInfo ai : appProcessList) { // KILL OTHER PROCESS OF MINE if (ai.uid == android.os.Process.myUid() && ai.pid != android.os.Process.myPid()) { android.os.Process.killProcess(ai.pid); } } } public static void killProcessExceptMain(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List appProcessList = am.getRunningAppProcesses(); if (appProcessList != null) { // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M // BUT THAT'S ENOUGH HERE for (ActivityManager.RunningAppProcessInfo ai : appProcessList) { if (ai.uid != android.os.Process.myUid()) { continue; } if (ai.processName.equals(context.getPackageName())) { continue; } android.os.Process.killProcess(ai.pid); } } } /** * add process name cache * * @param context * @return */ public static String getProcessName(Context context) { if (processName[0] == null) { synchronized (processName) { if (processName[0] == null) { processName[0] = getProcessNameInternal(context); } } } return (processName[0] != null ? processName[0] : ""); } @SuppressLint("NewApi") private static String getProcessNameInternal(final Context context) { if (isNewerOrEqualThanVersion(28, true)) { final String result = Application.getProcessName(); if (!TextUtils.isEmpty(result)) { return result; } } // The 'currentProcess' method only exists on api 18 and newer systems. if (isNewerOrEqualThanVersion(18, true)) { try { final Class activityThreadClazz = Class.forName("android.app.ActivityThread"); final Method currentProcessMethod = ShareReflectUtil.findMethod(activityThreadClazz, "currentProcessName"); currentProcessMethod.setAccessible(true); final String result = (String) currentProcessMethod.invoke(null); if (result != null && !result.isEmpty()) { return result; } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal reflect activity thread exception:" + thr.getMessage()); } } BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/cmdline"), StandardCharsets.US_ASCII)); String result = br.readLine(); if (result != null) { result = result.trim(); if (!result.isEmpty()) { return result; } } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal parse cmdline exception:" + thr.getMessage()); } finally { SharePatchFileUtil.closeQuietly(br); } if (context != null) { try { final int myPid = android.os.Process.myPid(); final int myUid = android.os.Process.myUid(); final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am != null) { final List procInfos = am.getRunningAppProcesses(); if (procInfos != null) { for (RunningAppProcessInfo procInfo : procInfos) { if (procInfo.pid == myPid && procInfo.uid == myUid) { return procInfo.processName; } } } } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal getRunningAppProcesses exception:" + thr.getMessage()); } } return null; } /** * vm whether it is art * * @return */ private static boolean isVmArt(String versionString) { boolean isArt = false; if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isArt = (major > 2) || ((major == 2) && (minor >= 1)); } catch (NumberFormatException e) { // let isMultidexCapable be false } } } return isArt; } private static boolean isVmJitInternal() { try { Class clazz = Class.forName("android.os.SystemProperties"); Method mthGet = clazz.getDeclaredMethod("get", String.class); String jit = (String) mthGet.invoke(null, "dalvik.vm.usejit"); String jitProfile = (String) mthGet.invoke(null, "dalvik.vm.usejitprofiles"); //usejit is true and usejitprofiles is null if (!isNullOrNil(jit) && isNullOrNil(jitProfile) && jit.equals("true")) { return true; } } catch (Throwable e) { ShareTinkerLog.e(TAG, "isVmJitInternal ex:" + e); } return false; } public static boolean isNewerOrEqualThanVersion(int apiLevel, boolean includePreviewVer) { if (includePreviewVer && Build.VERSION.SDK_INT >= 23) { return Build.VERSION.SDK_INT >= apiLevel || ((Build.VERSION.SDK_INT == apiLevel - 1) && Build.VERSION.PREVIEW_SDK_INT > 0); } else { return Build.VERSION.SDK_INT >= apiLevel; } } public static boolean isOlderOrEqualThanVersion(int apiLevel, boolean includePreviewVer) { if (includePreviewVer && Build.VERSION.SDK_INT >= 23) { return Build.VERSION.SDK_INT <= apiLevel || ((Build.VERSION.SDK_INT == apiLevel - 1) && Build.VERSION.PREVIEW_SDK_INT > 0); } else { return Build.VERSION.SDK_INT <= apiLevel; } } /** * @return true if system version is in range [lowApiLevel, hiApiLevel]. */ public static boolean isVersionInRange(int lowApiLevel, int hiApiLevel, boolean includePreviewVer) { return isNewerOrEqualThanVersion(lowApiLevel, includePreviewVer) && isOlderOrEqualThanVersion(hiApiLevel, includePreviewVer); } public static String getExceptionCauseString(final Throwable ex) { if (ex == null) return ""; final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(bos); try { // print directly Throwable t = ex; while (true) { Throwable cause = t.getCause(); if (cause == null) { break; } t = cause; } t.printStackTrace(ps); return toVisualString(bos.toString()); } finally { SharePatchFileUtil.closeQuietly(ps); } } public static String toVisualString(String src) { boolean cutFlg = false; if (null == src) { return null; } char[] chr = src.toCharArray(); if (null == chr) { return null; } int i = 0; for (; i < chr.length; i++) { if (chr[i] > 127) { chr[i] = 0; cutFlg = true; break; } } if (cutFlg) { return new String(chr, 0, i); } else { return src; } } public static void cleanPatch(Context context) { if (context == null) { throw new TinkerRuntimeException("context is null"); } final File tinkerDir = SharePatchFileUtil.getPatchDirectory(context); if (!tinkerDir.exists()) { ShareTinkerLog.printErrStackTrace(TAG, new Throwable(),"try to clean patch while there're not any applied patches."); return; } final File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(tinkerDir.getAbsolutePath()); if (!patchInfoFile.exists()) { ShareTinkerLog.printErrStackTrace(TAG, new Throwable(), "try to clean patch while patch info file does not exist."); return; } final File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(tinkerDir.getAbsolutePath()); final SharePatchInfo patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); if (patchInfo != null) { patchInfo.newVersion = ""; patchInfo.versionToRemove = ""; SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile); } else { ShareTinkerLog.printErrStackTrace(TAG, new Throwable(), "fail to get patchInfo."); } cleanPatchDirectoryWithGuard(tinkerDir, null); } /** * Clean obsolete patch version directories in {@code patchDirectory} which are not guarded. *

* See {@link Guard} for details about patch version guarding. * * @param patchDirectory Patch directory to be cleaned. * @param skipDirName Name of directory to be skipped, or null to skip nothing. */ public static void cleanPatchDirectoryWithGuard(File patchDirectory, String skipDirName) { final File guardDirectory = SharePatchFileUtil.getGuardDirectory(patchDirectory.getAbsolutePath()); final File[] allChildrenInPatchDir = patchDirectory.listFiles(); if (allChildrenInPatchDir != null) { for (final File child: allChildrenInPatchDir) { final String childName = child.getName(); if (!child.isDirectory() || !childName.startsWith(ShareConstants.PATCH_BASE_NAME)) { // skip non-patch directory. continue; } if (childName.equals(skipDirName)) { // Skip cleaning patch just created. continue; } final File guardLockFile = new File(guardDirectory, childName); if (!guardLockFile.exists()) { // Directory is broken, just clean. ShareTinkerLog.w(TAG, "clean broken patch directory %s", child.getAbsolutePath()); SharePatchFileUtil.deleteDir(child); } boolean cleaned; try (final Guard guard = Guard.acquireClean(guardLockFile)) { cleaned = guard != null; if (cleaned) { ShareTinkerLog.i(TAG, "clean obsolete patch directory %s", child.getAbsolutePath()); SharePatchFileUtil.deleteDir(child); } } if (cleaned) { guardLockFile.delete(); } else { ShareTinkerLog.i(TAG, "skip cleaning used patch directory %s", child.getAbsolutePath()); } } } } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerLog.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.os.Handler; import android.os.Message; import android.util.Log; import java.lang.reflect.Constructor; /** * Created by zhangshaowen on 16/3/17. */ public class ShareTinkerLog { private static final String TAG = "Tinker.ShareTinkerLog"; public static final int FN_LOG_PRINT_STACKTRACE = 0xFA1; public static final int FN_LOG_PRINT_PENDING_LOGS = 0xFA2; private static final Handler[] tinkerLogInlineFenceRef = {null}; private static final TinkerLogImp debugLog = new TinkerLogImp() { @Override public void v(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); android.util.Log.v(tag, log); } @Override public void i(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); android.util.Log.i(tag, log); } @Override public void d(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); android.util.Log.d(tag, log); } @Override public void w(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); android.util.Log.w(tag, log); } @Override public void e(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); android.util.Log.e(tag, log); } @Override public void printErrStackTrace(String tag, Throwable tr, String format, Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); if (log == null) { log = ""; } log += " " + android.util.Log.getStackTraceString(tr); android.util.Log.e(tag, log); } }; private static final TinkerLogImp[] tinkerLogImpRef = {debugLog}; static { synchronized (tinkerLogInlineFenceRef) { try { final Class clazz = Class.forName("com.tencent.tinker.loader.shareutil.TinkerLogInlineFence"); final Constructor ctor = clazz.getDeclaredConstructor(); ctor.setAccessible(true); tinkerLogInlineFenceRef[0] = (Handler) ctor.newInstance(); } catch (Throwable thr) { Log.e(TAG, "[-] Fail to create inline fence instance.", thr); tinkerLogInlineFenceRef[0] = null; } } } private static Handler getInlineFence() { synchronized (tinkerLogInlineFenceRef) { return tinkerLogInlineFenceRef[0]; } } public static TinkerLogImp getDefaultImpl() { return debugLog; } public static void setTinkerLogImp(TinkerLogImp imp) { synchronized (tinkerLogImpRef) { tinkerLogImpRef[0] = imp; if (imp != null && imp != debugLog) { printPendingLogs(); } } } public static TinkerLogImp getImpl() { synchronized (tinkerLogImpRef) { return tinkerLogImpRef[0]; } } public static void v(final String tag, final String fmt, final Object... values) { printLog(Log.VERBOSE, tag, fmt, values); } public static void d(final String tag, final String fmt, final Object... values) { printLog(Log.DEBUG, tag, fmt, values); } public static void i(final String tag, final String fmt, final Object... values) { printLog(Log.INFO, tag, fmt, values); } public static void w(final String tag, final String fmt, final Object... values) { printLog(Log.WARN, tag, fmt, values); } public static void e(final String tag, final String fmt, final Object... values) { printLog(Log.ERROR, tag, fmt, values); } public static void printErrStackTrace(String tag, Throwable thr, final String format, final Object... values) { printLog(tag, thr, format, values); } public static void printPendingLogs() { final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, FN_LOG_PRINT_PENDING_LOGS); inlineFence.handleMessage(msg); msg.recycle(); } } private static void printLog(int priority, String tag, String fmt, Object... values) { final long timestamp = System.currentTimeMillis(); final Object[] args = {priority, timestamp, tag, fmt, values}; final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, priority, args); inlineFence.handleMessage(msg); msg.recycle(); } else { debugLog.e(tag, "!! NO_LOG_IMPL !! Original Log: " + fmt, values); } } private static void printLog(String tag, Throwable thr, String fmt, Object... values) { final long timestamp = System.currentTimeMillis(); final Object[] args = {FN_LOG_PRINT_STACKTRACE, timestamp, tag, thr, fmt, values}; final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, FN_LOG_PRINT_STACKTRACE, args); inlineFence.handleMessage(msg); msg.recycle(); } else { debugLog.printErrStackTrace(tag, thr, "!! NO_LOG_IMPL !! Original Log: " + fmt, values); } } public interface TinkerLogImp { void v(final String tag, final String fmt, final Object... values); void d(final String tag, final String fmt, final Object... values); void i(final String tag, final String fmt, final Object... values); void w(final String tag, final String fmt, final Object... values); void e(final String tag, final String fmt, final Object... values); void printErrStackTrace(String tag, Throwable tr, final String format, final Object... values); } } ================================================ FILE: tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/TinkerLogInlineFence.java ================================================ package com.tencent.tinker.loader.shareutil; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.tencent.tinker.anno.Keep; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.FN_LOG_PRINT_PENDING_LOGS; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.FN_LOG_PRINT_STACKTRACE; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.getDefaultImpl; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.getImpl; /** * Created by tangyinsheng on 2020/6/4. */ @Keep final class TinkerLogInlineFence extends Handler { private static final String TAG = "Tinker.TinkerLogInlineFence"; private static final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); private static final List pendingLogs = new ArrayList<>(); @Override public void handleMessage(Message msg) { handleMessage_$noinline$(msg); } private void handleMessage_$noinline$(Message msg) { try { dummyThrowExceptionMethod(); } finally { handleMessageImpl(msg); } } private void handleMessageImpl(Message msg) { final ShareTinkerLog.TinkerLogImp defaultLogImp = getDefaultImpl(); final ShareTinkerLog.TinkerLogImp logImp = getImpl(); switch (msg.what) { case Log.VERBOSE: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.v((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.DEBUG: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.d((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.INFO: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.i((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.WARN: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.w((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.ERROR: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.e((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case FN_LOG_PRINT_STACKTRACE: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.printErrStackTrace((String) args[2], (Throwable) args[3], (String) args[4], (Object[]) args[5]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case FN_LOG_PRINT_PENDING_LOGS: { printPendingLogs(logImp); break; } default: { logImp.e(TAG, "[-] Bad msg id: " + msg.what); break; } } } private static void printPendingLogs(final ShareTinkerLog.TinkerLogImp logImp) { synchronized (pendingLogs) { if (logImp == null || pendingLogs.isEmpty()) { return; } } new Thread(new Runnable() { @Override public void run() { final SimpleDateFormat timestampFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH); synchronized (pendingLogs) { for (Object[] args : pendingLogs) { final Object[] argsRef = args; mainThreadHandler.post(new Runnable() { @Override public void run() { final String timestamp = timestampFmt.format(new Date((long) argsRef[1])); final String prefix = "[PendingLog @ " + timestamp + "] "; switch ((int) argsRef[0]) { case Log.VERBOSE: { logImp.v((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.DEBUG: { logImp.d((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.INFO: { logImp.i((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.WARN: { logImp.w((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.ERROR: { logImp.e((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case ShareTinkerLog.FN_LOG_PRINT_STACKTRACE: { logImp.printErrStackTrace((String) argsRef[2], (Throwable) argsRef[3], prefix + (String) argsRef[4], (Object[]) argsRef[5]); break; } default: { // Ignored. break; } } } }); } pendingLogs.clear(); } } }, "tinker_log_printer").start(); } private static void dummyThrowExceptionMethod() { if (TinkerLogInlineFence.class.isPrimitive()) { throw new RuntimeException(); } } } ================================================ FILE: tinker-android/tinker-android-loader/src/test/java/com/tencent/tinker/loader/ExampleUnitTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: tinker-android/tinker-android-loader/src/test/java/com/tencent/tinker/loader/shareutil/GuardTest.java ================================================ package com.tencent.tinker.loader.shareutil; import org.junit.Assert; import org.junit.Test; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.concurrent.CountDownLatch; public class GuardTest { @Test public void testAcquireClean() throws IOException { final File guardLockFile = Files.createTempFile("tinker_guard_", ".lock").toFile(); final CountDownLatch latchJoin = new CountDownLatch(2); final CountDownLatch[] latches = new CountDownLatch[] { new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1) }; new Thread(() -> { final Guard useGuard = Guard.acquireUse(guardLockFile); Assert.assertNotNull(useGuard); latches[0].countDown(); try { latches[1].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } useGuard.close(); latches[2].countDown(); latchJoin.countDown(); }).start(); new Thread(() -> { try { latches[0].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } final Guard cleanGuardWhileUse = Guard.acquireClean(guardLockFile); Assert.assertNull(cleanGuardWhileUse); latches[1].countDown(); try { latches[2].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } final Guard cleanGuardWhileNotUse = Guard.acquireClean(guardLockFile); Assert.assertNotNull(cleanGuardWhileNotUse); cleanGuardWhileNotUse.close(); latchJoin.countDown(); }).start(); try { latchJoin.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Test public void testAcquireUse() throws IOException { final File guardLockFile = Files.createTempFile("tinker_guard_", ".lock").toFile(); final CountDownLatch latchJoin = new CountDownLatch(2); final CountDownLatch[] latches = new CountDownLatch[] { new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1) }; new Thread(() -> { final Guard useGuardBeforeClean = Guard.acquireUse(guardLockFile); Assert.assertNotNull(useGuardBeforeClean); useGuardBeforeClean.close(); latches[0].countDown(); try { latches[1].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } final Guard useGuardWhileClean = Guard.acquireUse(guardLockFile); Assert.assertNull(useGuardWhileClean); latches[2].countDown(); try { latches[3].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } final Guard useGuardAfterClean = Guard.acquireUse(guardLockFile); Assert.assertNull(useGuardAfterClean); latchJoin.countDown(); }).start(); new Thread(() -> { try { latches[0].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } final Guard cleanGuard = Guard.acquireClean(guardLockFile); Assert.assertNotNull(cleanGuard); latches[1].countDown(); try { latches[2].await(); } catch (InterruptedException e) { throw new RuntimeException(e); } cleanGuard.close(); latches[3].countDown(); latchJoin.countDown(); }).start(); try { latchJoin.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/.gitignore ================================================ /build ================================================ FILE: tinker-android/tinker-android-loader-no-op/build.gradle ================================================ apply plugin: 'com.android.library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion buildConfigField "String", "TINKER_VERSION", "\"${rootProject.ext.VERSION_NAME}\"" manifestPlaceholders = [TINKER_VERSION: "${rootProject.ext.VERSION_NAME}"] consumerProguardFiles file('../consumer-proguard.txt') } lintOptions { disable 'LongLogTag' } compileOptions { sourceCompatibility rootProject.ext.javaVersion targetCompatibility rootProject.ext.javaVersion } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) testImplementation 'junit:junit:4.12' implementation project(':tinker-android:tinker-android-anno-support') } task buildTinkerSdk(type: Copy, dependsOn: [build]) { group = "tinker" from("$buildDir/outputs/aar/") { include "${project.getName()}-release.aar" } into(rootProject.file("buildSdk/android/")) rename { String fileName -> fileName.replace("release", "${version}") } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-android/tinker-android-loader-no-op/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-android-loader-no-op POM_NAME=Tinker Android Loader POM_PACKAGING=jar ================================================ FILE: tinker-android/tinker-android-loader-no-op/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/zhangshaowen/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/androidTest/java/com/tencent/tinker/loader/ApplicationTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/AndroidManifest.xml ================================================ ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/entry/ApplicationLifeCycle.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; /** * Created by zhangshaowen on 16/3/8. */ import android.app.Application; import android.content.Context; import android.content.res.Configuration; /** * This interface is used to delegate calls from main Application object. * * Implementations of this interface must have a one-argument constructor that takes * an argument of type {@link Application}. */ public interface ApplicationLifeCycle { /** * Same as {@link Application#onCreate()}. */ void onCreate(); /** * Same as {@link Application#onLowMemory()}. */ void onLowMemory(); /** * Same as {@link Application#onTrimMemory(int level)}. * @param level */ void onTrimMemory(int level); /** * Same as {@link Application#onTerminate()}. */ void onTerminate(); /** * Same as {@link Application#onConfigurationChanged(Configuration newconfig)}. */ void onConfigurationChanged(Configuration newConfig); /** * Same as {@link Application#attachBaseContext(Context context)}. */ void onBaseContextAttached(Context base); } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/entry/ApplicationLike.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import com.tencent.tinker.anno.Keep; /** * Created by zhangshaowen on 16/7/28. */ @Keep public abstract class ApplicationLike implements ApplicationLifeCycle { private final Application application; private final Intent tinkerResultIntent; private final long applicationStartElapsedTime; private final long applicationStartMillisTime; private final int tinkerFlags; private final boolean tinkerLoadVerifyFlag; public ApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { this.application = application; this.tinkerFlags = tinkerFlags; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.applicationStartElapsedTime = applicationStartElapsedTime; this.applicationStartMillisTime = applicationStartMillisTime; this.tinkerResultIntent = tinkerResultIntent; } public Application getApplication() { return application; } public final Intent getTinkerResultIntent() { return tinkerResultIntent; } public final int getTinkerFlags() { return tinkerFlags; } public final boolean getTinkerLoadVerifyFlag() { return tinkerLoadVerifyFlag; } public long getApplicationStartElapsedTime() { return applicationStartElapsedTime; } public long getApplicationStartMillisTime() { return applicationStartMillisTime; } @Override public void onCreate() { } @Override public void onLowMemory() { } @Override public void onTrimMemory(int level) { } @Override public void onTerminate() { } @Override public void onConfigurationChanged(Configuration newConfig) { } @Override public void onBaseContextAttached(Context base) { } //some get methods that may be overwrite @Keep public Resources getResources(Resources resources) { return resources; } @Keep public ClassLoader getClassLoader(ClassLoader classLoader) { return classLoader; } @Keep public AssetManager getAssets(AssetManager assetManager) { return assetManager; } @Keep public Object getSystemService(String name, Object service) { return service; } @Keep public Context getBaseContext(Context base) { return base; } @Keep public Theme getTheme(Theme theme) { return theme; } @Keep public int mzNightModeUseOf() { // Return 1 for default according to MeiZu's announcement. return 1; } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/entry/DefaultApplicationLike.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.entry; /** * Created by zhangshaowen on 16/3/8. */ import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import com.tencent.tinker.anno.Keep; import com.tencent.tinker.loader.shareutil.ShareTinkerLog; /** * Empty implementation of {@link ApplicationLike}. */ @Keep public class DefaultApplicationLike extends ApplicationLike { private static final String TAG = "Tinker.DefaultAppLike"; public DefaultApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @Override public void onCreate() { ShareTinkerLog.d(TAG, "onCreate"); } @Override public void onLowMemory() { ShareTinkerLog.d(TAG, "onLowMemory"); } @Override public void onTrimMemory(int level) { ShareTinkerLog.d(TAG, "onTrimMemory level:" + level); } @Override public void onTerminate() { ShareTinkerLog.d(TAG, "onTerminate"); } @Override public void onConfigurationChanged(Configuration newConfig) { ShareTinkerLog.d(TAG, "onConfigurationChanged:" + newConfig.toString()); } @Override public void onBaseContextAttached(Context base) { ShareTinkerLog.d(TAG, "onBaseContextAttached:"); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/TinkerRuntimeException.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; /** * Created by zhangshaowen on 16/7/8. */ public class TinkerRuntimeException extends RuntimeException { private static final String TINKER_RUNTIME_EXCEPTION_PREFIX = "Tinker Exception:"; private static final long serialVersionUID = 1L; public TinkerRuntimeException(String detailMessage) { super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage); } public TinkerRuntimeException(String detailMessage, Throwable throwable) { super(TINKER_RUNTIME_EXCEPTION_PREFIX + detailMessage, throwable); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/app/TinkerApplication.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.app; import android.annotation.TargetApi; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.os.SystemClock; import com.tencent.tinker.anno.Keep; import com.tencent.tinker.entry.ApplicationLike; import com.tencent.tinker.loader.TinkerRuntimeException; import com.tencent.tinker.loader.shareutil.ShareConstants; import com.tencent.tinker.loader.shareutil.ShareIntentUtil; import java.lang.reflect.Constructor; /** * Created by zhangshaowen on 16/3/8. */ public abstract class TinkerApplication extends Application { private static final TinkerApplication[] SELF_HOLDER = {null}; private final int tinkerFlags; private final boolean tinkerLoadVerifyFlag; private final String delegateClassName; private final boolean useDelegateLastClassLoader; private final boolean useInterpretModeOnSupported32BitSystem; /** * if we have load patch, we should use safe mode */ protected Intent tinkerResultIntent; protected ClassLoader mCurrentClassLoader = null; private ApplicationLike mAppLike = null; protected TinkerApplication(int tinkerFlags) { this(ShareConstants.TINKER_DISABLE, "com.tencent.tinker.entry.DefaultApplicationLike"); } protected TinkerApplication(int tinkerFlags, String delegateClassName) { this(ShareConstants.TINKER_DISABLE, delegateClassName, null, false, false, false); } protected TinkerApplication(int tinkerFlags, String delegateClassName, String loaderClassName, boolean tinkerLoadVerifyFlag, boolean useDelegateLastClassLoader, boolean useInterpretModeOnSupported32BitSystem) { synchronized (SELF_HOLDER) { SELF_HOLDER[0] = this; } this.tinkerFlags = ShareConstants.TINKER_DISABLE; this.delegateClassName = delegateClassName; this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag; this.useDelegateLastClassLoader = useDelegateLastClassLoader; this.useInterpretModeOnSupported32BitSystem = useInterpretModeOnSupported32BitSystem; } public static TinkerApplication getInstance() { synchronized (SELF_HOLDER) { if (SELF_HOLDER[0] == null) { throw new IllegalStateException("TinkerApplication is not initialized."); } return SELF_HOLDER[0]; } } private ApplicationLike createDelegate(Application app, int tinkerFlags, String delegateClassName, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent resultIntent) { try { // Use reflection to create the delegate so it doesn't need to go into the primary dex. // And we can also patch it final Class delegateClass = Class.forName(delegateClassName, false, mCurrentClassLoader); final Constructor constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class, Intent.class); return (ApplicationLike) constructor.newInstance(app, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, resultIntent); } catch (Throwable thr) { throw new TinkerRuntimeException("createDelegate failed", thr); } } protected void onBaseContextAttached(Context base, long applicationStartElapsedTime, long applicationStartMillisTime) { try { mCurrentClassLoader = base.getClassLoader(); this.tinkerResultIntent = new Intent(); ShareIntentUtil.setIntentReturnCode(this.tinkerResultIntent, ShareConstants.ERROR_LOAD_DISABLE); mAppLike = createDelegate(this, tinkerFlags, delegateClassName, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); mAppLike.onBaseContextAttached(base); } catch (TinkerRuntimeException e) { throw e; } catch (Throwable thr) { throw new TinkerRuntimeException(thr.getMessage(), thr); } } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); final long applicationStartElapsedTime = SystemClock.elapsedRealtime(); final long applicationStartMillisTime = System.currentTimeMillis(); onBaseContextAttached(base, applicationStartElapsedTime, applicationStartMillisTime); } @Override public void onCreate() { super.onCreate(); if (mAppLike != null) { mAppLike.onCreate(); } } @Override public void onTerminate() { super.onTerminate(); if (mAppLike != null) { mAppLike.onTerminate(); } } @Override public void onLowMemory() { super.onLowMemory(); if (mAppLike != null) { mAppLike.onLowMemory(); } } @TargetApi(14) @Override public void onTrimMemory(int level) { super.onTrimMemory(level); if (mAppLike != null) { mAppLike.onTrimMemory(level); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mAppLike != null) { mAppLike.onConfigurationChanged(newConfig); } } @Override public Resources getResources() { final Resources resources = super.getResources(); if (mAppLike != null) { return mAppLike.getResources(resources); } else { return resources; } } @Override public ClassLoader getClassLoader() { final ClassLoader classLoader = super.getClassLoader(); if (mAppLike != null) { return mAppLike.getClassLoader(classLoader); } else { return classLoader; } } @Override public AssetManager getAssets() { final AssetManager assets = super.getAssets(); if (mAppLike != null) { return mAppLike.getAssets(assets); } else { return assets; } } @Override public Object getSystemService(String name) { final Object service = super.getSystemService(name); if (mAppLike != null) { return mAppLike.getSystemService(name, service); } else { return service; } } @Override public Context getBaseContext() { final Context base = super.getBaseContext(); if (mAppLike != null) { return mAppLike.getBaseContext(base); } else { return base; } } @Override public Theme getTheme() { final Theme theme = super.getTheme(); if (mAppLike != null) { return mAppLike.getTheme(theme); } return theme; } @Keep public int mzNightModeUseOf() { if (mAppLike != null) { return mAppLike.mzNightModeUseOf(); } else { // Return 1 for default according to MeiZu's announcement. return 1; } } public void setUseSafeMode(boolean useSafeMode) { // Ignored. } public boolean isTinkerLoadVerifyFlag() { return false; } public int getTinkerFlags() { return tinkerFlags; } public boolean isUseDelegateLastClassLoader() { return useDelegateLastClassLoader; } public boolean isUseInterpretModeOnSupported32BitSystem() { return useInterpretModeOnSupported32BitSystem; } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareArkHotDiffPatchInfo.java ================================================ /* * Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.loader.shareutil; import java.util.ArrayList; public class ShareArkHotDiffPatchInfo { public String path; public String name; public String patchMd5; public ShareArkHotDiffPatchInfo(String path, String name, String md5) { this.name = name; this.patchMd5 = md5; this.path = path; } public static void parseDiffPatchInfo(String meta, ArrayList diffList) { if (meta == null || diffList == null) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 4); if (kv == null || kv.length < 3) { continue; } final String name = kv[0].trim(); final String path = kv[1].trim(); final String md5 = kv[2].trim(); ShareArkHotDiffPatchInfo arkDiffInfo = new ShareArkHotDiffPatchInfo(path, name, md5); diffList.add(arkDiffInfo); } } public static boolean checkDiffPatchInfo(ShareArkHotDiffPatchInfo info) { if (info == null) { return false; } String name = info.name; String md5 = info.patchMd5; if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(name); sb.append(","); sb.append(path); sb.append(","); sb.append(patchMd5); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareBsDiffPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.util.ArrayList; /** * patch via bsdiff * Created by zhangshaowen on 16/3/16. */ public class ShareBsDiffPatchInfo { public String name; public String md5; public String rawCrc; public String patchMd5; public String path; public ShareBsDiffPatchInfo(String name, String md5, String path, String raw, String patch) { // TODO Auto-generated constructor stub this.name = name; this.md5 = md5; this.rawCrc = raw; this.patchMd5 = patch; this.path = path; } public static void parseDiffPatchInfo(String meta, ArrayList diffList) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 5); if (kv == null || kv.length < 5) { continue; } // key final String name = kv[0].trim(); final String path = kv[1].trim(); final String md5 = kv[2].trim(); final String rawCrc = kv[3].trim(); final String patchMd5 = kv[4].trim(); ShareBsDiffPatchInfo dexInfo = new ShareBsDiffPatchInfo(name, md5, path, rawCrc, patchMd5); diffList.add(dexInfo); } } public static boolean checkDiffPatchInfo(ShareBsDiffPatchInfo info) { if (info == null) { return false; } String name = info.name; String md5 = info.md5; if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(name); sb.append(","); sb.append(path); sb.append(","); sb.append(md5); sb.append(","); sb.append(rawCrc); sb.append(","); sb.append(patchMd5); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareConstants.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.BuildConfig; import java.util.regex.Pattern; /** * Created by zhangshaowen on 16/3/24. */ public class ShareConstants { public static final String TINKER_VERSION = BuildConfig.TINKER_VERSION; public static final int BUFFER_SIZE = 16384; public static final int MD5_LENGTH = 32; public static final int MD5_FILE_BUF_LENGTH = 1024 * 100; public static final int MAX_EXTRACT_ATTEMPTS = 2; public static final String TINKER_ID = "TINKER_ID"; public static final String NEW_TINKER_ID = "NEW_TINKER_ID"; // Please keep it synchronized with the one defined in TypedValue. public static final String PKGMETA_KEY_IS_PROTECTED_APP = "is_protected_app"; public static final String OLD_VERSION = "old"; public static final String NEW_VERSION = "new"; public static final String PATCH_BASE_NAME = "patch-"; public static final String PATCH_SUFFIX = ".apk"; public static final String PACKAGE_META_FILE = "assets/package_meta.txt"; public static final String SO_META_FILE = "assets/so_meta.txt"; public static final String SO_PATH = "lib"; public static final String DEX_META_FILE = "assets/dex_meta.txt"; public static final String DEX_PATH = "dex"; public static final String ARKHOTFIX_PATH = "arkHot"; public static final String DEFAULT_DEX_OPTIMIZE_PATH = "odex"; public static final String ANDROID_O_DEX_OPTIMIZE_PATH = "oat"; public static final String INTERPRET_DEX_OPTIMIZE_PATH = "interpet"; public static final String CHANING_DEX_OPTIMIZE_PATH = "changing"; public static final String DEX_SUFFIX = ".dex"; public static final String JAR_SUFFIX = ".jar"; public static final String APK_SUFFIX = ".apk"; public static final String ODEX_SUFFIX = ".odex"; public static final String TEST_DEX_NAME = "test.dex"; public static final String CLASS_N_APK_NAME = "tinker_classN.apk"; public static final String ARKHOT_PATCH_NAME = "patch.apk"; public static final Pattern CLASS_N_PATTERN = Pattern.compile("classes(?:[2-9]?|[1-9][0-9]+)\\.dex(\\.jar)?"); public static final String CHECK_DEX_INSTALL_FAIL = "checkDexInstall failed"; public static final String CHECK_RES_INSTALL_FAIL = "checkResInstall failed"; public static final String CHECK_DEX_OAT_EXIST_FAIL = "checkDexOptExist failed"; public static final String CHECK_DEX_OAT_FORMAT_FAIL = "checkDexOptFormat failed"; // public static final String CHECK_VM_PROPERTY_FAIL = "checkVmArtProperty failed"; public static final String RES_META_FILE = "assets/res_meta.txt"; public static final String RES_ARSC = "resources.arsc"; public static final String RES_MANIFEST = "AndroidManifest.xml"; public static final String RES_TITLE = "resources_out.zip"; public static final String RES_PATH = "res"; public static final String RES_NAME = "resources.apk"; public static final String RES_ADD_TITLE = "add:"; public static final String RES_MOD_TITLE = "modify:"; public static final String RES_LARGE_MOD_TITLE = "large modify:"; public static final String RES_DEL_TITLE = "delete:"; public static final String RES_PATTERN_TITLE = "pattern:"; public static final String RES_STORE_TITLE = "store:"; public static final String ARKHOT_META_FILE = "assets/arkHot_meta.txt"; public static final String DEXMODE_RAW = "raw"; public static final String DEXMODE_JAR = "jar"; public static final String DEX_IN_JAR = "classes.dex"; public static final String PATCH_DIRECTORY_NAME = "tinker"; public static final String PATCH_DIRECTORY_NAME_SPEC = "wc_tinker_dir"; public static final String PATCH_TEMP_DIRECTORY_NAME = "tinker_temp"; public static final String PATCH_TEMP_LAST_CRASH_NAME = "tinker_last_crash"; public static final String PATCH_INFO_NAME = "patch_meta.info"; public static final String PATCH_INFO_LOCK_NAME = "info.lock"; public static final String META_SUFFIX = "meta.txt"; /** * multi process share */ public static final String TINKER_SHARE_PREFERENCE_CONFIG = "tinker_share_config"; public static final String TINKER_ENABLE_CONFIG_PREFIX = "tinker_enable_"; /** * only for each process */ public static final String TINKER_OWN_PREFERENCE_CONFIG_PREFIX = "tinker_own_config_"; public static final String TINKER_SAFE_MODE_COUNT_PREFIX = "safe_mode_count_"; public static final int TINKER_SAFE_MODE_MAX_COUNT = 3; /** * notification id, use to Increasing the patch process priority * your app shouldn't use the same notification id. * if you want to define it, use {@code TinkerPatchService.setTinkerNotificationId} */ public static final int TINKER_PATCH_SERVICE_NOTIFICATION = -1119860829; //resource type public static final int TYPE_PATCH_FILE = 1; public static final int TYPE_PATCH_INFO = 2; public static final int TYPE_DEX = 3; public static final int TYPE_DEX_OPT = 4; public static final int TYPE_LIBRARY = 5; public static final int TYPE_RESOURCE = 6; public static final int TYPE_CLASS_N_DEX = 7; public static final int TYPE_ARKHOT_SO = 8; public static final int TINKER_DISABLE = 0x00; public static final int TINKER_DEX_MASK = 0x01; public static final int TINKER_NATIVE_LIBRARY_MASK = 0x02; public static final int TINKER_RESOURCE_MASK = 0x04; public static final int TINKER_ARKHOT_MASK = 0x08; public static final int TINKER_DEX_AND_LIBRARY = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_ARKHOT_MASK; public static final int TINKER_ENABLE_ALL = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_RESOURCE_MASK | TINKER_ARKHOT_MASK; //load error code public static final int ERROR_LOAD_OK = 0; public static final int ERROR_LOAD_DISABLE = -1; public static final int ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST = -2; public static final int ERROR_LOAD_PATCH_INFO_NOT_EXIST = -3; public static final int ERROR_LOAD_PATCH_INFO_CORRUPTED = -4; public static final int ERROR_LOAD_PATCH_INFO_BLANK = -5; public static final int ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST = -6; public static final int ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST = -7; public static final int ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL = -8; public static final int ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST = -9; public static final int ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST = -10; public static final int ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST = -11; public static final int ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL = -12; public static final int ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH = -13; public static final int ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION = -14; public static final int ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION = -15; public static final int ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION = -16; public static final int ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST = -17; public static final int ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST = -18; public static final int ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL = -19; public static final int ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION = -20; //resource public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST = -21; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST = -22; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION = -23; public static final int ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH = -24; public static final int ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION = -25; public static final int ERROR_LOAD_GET_INTENT_FAIL = -10000; //load exception code //recover error code public static final int ERROR_LOAD_EXCEPTION_UNKNOWN = -1; public static final int ERROR_LOAD_EXCEPTION_DEX = -2; public static final int ERROR_LOAD_EXCEPTION_RESOURCE = -3; public static final int ERROR_LOAD_EXCEPTION_UNCAUGHT = -4; public static final int ERROR_LOAD_EXCEPTION_COMPONENT_HOTPLUG = -5; //patch listener error code public static final int ERROR_PATCH_OK = 0; public static final int ERROR_PATCH_DISABLE = -1; public static final int ERROR_PATCH_NOTEXIST = -2; public static final int ERROR_PATCH_RUNNING = -3; public static final int ERROR_PATCH_INSERVICE = -4; public static final int ERROR_PATCH_JIT = -5; public static final int ERROR_PATCH_ALREADY_APPLY = -6; public static final int ERROR_PATCH_RETRY_COUNT_LIMIT = -7; //package check error code public static final int ERROR_PACKAGE_CHECK_OK = 0; public static final int ERROR_PACKAGE_CHECK_SIGNATURE_FAIL = -1; public static final int ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND = -2; public static final int ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED = -3; public static final int ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED = -4; public static final int ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND = -5; public static final int ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = -6; public static final int ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL = -7; public static final int ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED = -8; public static final int ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT = -9; // interpret error type public static final int TYPE_INTERPRET_OK = 0; public static final int TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR = 1; public static final int TYPE_INTERPRET_COMMAND_ERROR = 2; } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareDexDiffPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.TinkerRuntimeException; import java.util.ArrayList; /** * Created by zhangshaowen on 16/4/11. */ public class ShareDexDiffPatchInfo { public final String rawName; public final String destMd5InDvm; public final String destMd5InArt; public final String oldDexCrC; public final String newOrPatchedDexCrC; public final String dexDiffMd5; public final String path; public final String dexMode; public final boolean isJarMode; /** * if it is jar mode, and the name is end of .dex, we should repackage it with zip, with renaming name.dex.jar */ public final String realName; public ShareDexDiffPatchInfo(String name, String path, String destMd5InDvm, String destMd5InArt, String dexDiffMd5, String oldDexCrc, String newOrPatchedDexCrC, String dexMode) { // TODO Auto-generated constructor stub this.rawName = name; this.path = path; this.destMd5InDvm = destMd5InDvm; this.destMd5InArt = destMd5InArt; this.dexDiffMd5 = dexDiffMd5; this.oldDexCrC = oldDexCrc; this.newOrPatchedDexCrC = newOrPatchedDexCrC; this.dexMode = dexMode; if (dexMode.equals(ShareConstants.DEXMODE_JAR)) { this.isJarMode = true; if (SharePatchFileUtil.isRawDexFile(name)) { realName = name + ShareConstants.JAR_SUFFIX; } else { realName = name; } } else if (dexMode.equals(ShareConstants.DEXMODE_RAW)) { this.isJarMode = false; this.realName = name; } else { throw new TinkerRuntimeException("can't recognize dex mode:" + dexMode); } } public static void parseDexDiffPatchInfo(String meta, ArrayList dexList) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } final String[] kv = line.split(",", 8); if (kv == null || kv.length < 8) { continue; } // key final String name = kv[0].trim(); final String path = kv[1].trim(); final String destMd5InDvm = kv[2].trim(); final String destMd5InArt = kv[3].trim(); final String dexDiffMd5 = kv[4].trim(); final String oldDexCrc = kv[5].trim(); final String newDexCrc = kv[6].trim(); final String dexMode = kv[7].trim(); ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt, dexDiffMd5, oldDexCrc, newDexCrc, dexMode); dexList.add(dexInfo); } } public static boolean checkDexDiffPatchInfo(ShareDexDiffPatchInfo info) { if (info == null) { return false; } String name = info.rawName; String md5 = (ShareTinkerInternals.isVmArt() ? info.destMd5InArt : info.destMd5InDvm); if (name == null || name.length() <= 0 || md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(rawName); sb.append(","); sb.append(path); sb.append(","); sb.append(destMd5InDvm); sb.append(","); sb.append(destMd5InArt); sb.append(","); sb.append(oldDexCrC); sb.append(","); sb.append(newOrPatchedDexCrC); sb.append(","); sb.append(dexDiffMd5); sb.append(","); sb.append(dexMode); return sb.toString(); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * Created by tangyinsheng on 2017/3/13. */ public class ShareElfFile implements Closeable { public static final int FILE_TYPE_OTHERS = -1; public static final int FILE_TYPE_ODEX = 0; public static final int FILE_TYPE_ELF = 1; private final FileInputStream fis; private final Map sectionNameToHeaderMap = new HashMap<>(); public ElfHeader elfHeader = null; public ProgramHeader[] programHeaders = null; public SectionHeader[] sectionHeaders = null; public ShareElfFile(File file) throws IOException { fis = new FileInputStream(file); final FileChannel channel = fis.getChannel(); elfHeader = new ElfHeader(channel); final ByteBuffer headerBuffer = ByteBuffer.allocate(128); headerBuffer.limit(elfHeader.ePhEntSize); headerBuffer.order(elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); channel.position(elfHeader.ePhOff); programHeaders = new ProgramHeader[elfHeader.ePhNum]; for (int i = 0; i < programHeaders.length; ++i) { readUntilLimit(channel, headerBuffer, "failed to read phdr."); programHeaders[i] = new ProgramHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]); } channel.position(elfHeader.eShOff); headerBuffer.limit(elfHeader.eShEntSize); sectionHeaders = new SectionHeader[elfHeader.eShNum]; for (int i = 0; i < sectionHeaders.length; ++i) { readUntilLimit(channel, headerBuffer, "failed to read shdr."); sectionHeaders[i] = new SectionHeader(headerBuffer, elfHeader.eIndent[ElfHeader.EI_CLASS]); } if (elfHeader.eShStrNdx > 0) { final SectionHeader shStrTabSectionHeader = sectionHeaders[elfHeader.eShStrNdx]; final ByteBuffer shStrTab = getSection(shStrTabSectionHeader); for (SectionHeader shdr : sectionHeaders) { shStrTab.position(shdr.shName); shdr.shNameStr = readCString(shStrTab); sectionNameToHeaderMap.put(shdr.shNameStr, shdr); } } } private static void assertInRange(int b, int lb, int ub, String errMsg) throws IOException { if (b < lb || b > ub) { throw new IOException(errMsg); } } public static int getFileTypeByMagic(File file) throws IOException { InputStream is = null; try { final byte[] magicBuf = new byte[4]; is = new FileInputStream(file); is.read(magicBuf); if (magicBuf[0] == 'd' && magicBuf[1] == 'e' && magicBuf[2] == 'y' && magicBuf[3] == '\n') { return FILE_TYPE_ODEX; } else if (magicBuf[0] == 0x7F && magicBuf[1] == 'E' && magicBuf[2] == 'L' && magicBuf[3] == 'F') { return FILE_TYPE_ELF; } else { return FILE_TYPE_OTHERS; } } finally { if (is != null) { try { is.close(); } catch (Throwable thr) { // Ignored. } } } } public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException { bufferOut.rewind(); int bytesRead = channel.read(bufferOut); if (bytesRead != bufferOut.limit()) { throw new IOException(errMsg + " Rest bytes insufficient, expect to read " + bufferOut.limit() + " bytes but only " + bytesRead + " bytes were read."); } bufferOut.flip(); } public static String readCString(ByteBuffer buffer) { final byte[] rawBuffer = buffer.array(); int begin = buffer.position(); while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) { buffer.position(buffer.position() + 1); } // Move to the start of next cstring. buffer.position(buffer.position() + 1); return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName("ASCII")); } public FileChannel getChannel() { return fis.getChannel(); } public boolean is32BitElf() { return (elfHeader.eIndent[ElfHeader.EI_CLASS] == ElfHeader.ELFCLASS32); } public ByteOrder getDataOrder() { return (elfHeader.eIndent[ElfHeader.EI_DATA] == ElfHeader.ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); } public SectionHeader getSectionHeaderByName(String name) { return sectionNameToHeaderMap.get(name); } public ByteBuffer getSection(SectionHeader sectionHeader) throws IOException { final ByteBuffer result = ByteBuffer.allocate((int) sectionHeader.shSize); fis.getChannel().position(sectionHeader.shOffset); readUntilLimit(fis.getChannel(), result, "failed to read section: " + sectionHeader.shNameStr); return result; } public ByteBuffer getSegment(ProgramHeader programHeader) throws IOException { final ByteBuffer result = ByteBuffer.allocate((int) programHeader.pFileSize); fis.getChannel().position(programHeader.pOffset); readUntilLimit(fis.getChannel(), result, "failed to read segment (type: " + programHeader.pType + ")."); return result; } @Override public void close() throws IOException { fis.close(); sectionNameToHeaderMap.clear(); programHeaders = null; sectionHeaders = null; } public static class ElfHeader { // Elf indent field index. public static final int EI_CLASS = 4; public static final int EI_DATA = 5; public static final int EI_VERSION = 6; // Elf classes. public static final int ELFCLASS32 = 1; public static final int ELFCLASS64 = 2; // Elf data encoding. public static final int ELFDATA2LSB = 1; public static final int ELFDATA2MSB = 2; // Elf types. public static final int ET_NONE = 0; public static final int ET_REL = 1; public static final int ET_EXEC = 2; public static final int ET_DYN = 3; public static final int ET_CORE = 4; public static final int ET_LOPROC = 0xff00; public static final int ET_HIPROC = 0xffff; // Elf indent version. public static final int EV_CURRENT = 1; private static final int EI_NINDENT = 16; public final byte[] eIndent = new byte[EI_NINDENT]; public final short eType; public final short eMachine; public final int eVersion; public final long eEntry; public final long ePhOff; public final long eShOff; public final int eFlags; public final short eEhSize; public final short ePhEntSize; public final short ePhNum; public final short eShEntSize; public final short eShNum; public final short eShStrNdx; private ElfHeader(FileChannel channel) throws IOException { channel.position(0); channel.read(ByteBuffer.wrap(eIndent)); if (eIndent[0] != 0x7F || eIndent[1] != 'E' || eIndent[2] != 'L' || eIndent[3] != 'F') { throw new IOException(String.format("bad elf magic: %x %x %x %x.", eIndent[0], eIndent[1], eIndent[2], eIndent[3])); } assertInRange(eIndent[EI_CLASS], ELFCLASS32, ELFCLASS64, "bad elf class: " + eIndent[EI_CLASS]); assertInRange(eIndent[EI_DATA], ELFDATA2LSB, ELFDATA2MSB, "bad elf data encoding: " + eIndent[EI_DATA]); final ByteBuffer restBuffer = ByteBuffer.allocate(eIndent[EI_CLASS] == ELFCLASS32 ? 36 : 48); restBuffer.order(eIndent[EI_DATA] == ELFDATA2LSB ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); readUntilLimit(channel, restBuffer, "failed to read rest part of ehdr."); eType = restBuffer.getShort(); eMachine = restBuffer.getShort(); eVersion = restBuffer.getInt(); assertInRange(eVersion, EV_CURRENT, EV_CURRENT, "bad elf version: " + eVersion); switch (eIndent[EI_CLASS]) { case ELFCLASS32: eEntry = restBuffer.getInt(); ePhOff = restBuffer.getInt(); eShOff = restBuffer.getInt(); break; case ELFCLASS64: eEntry = restBuffer.getLong(); ePhOff = restBuffer.getLong(); eShOff = restBuffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + eIndent[EI_CLASS]); } eFlags = restBuffer.getInt(); eEhSize = restBuffer.getShort(); ePhEntSize = restBuffer.getShort(); ePhNum = restBuffer.getShort(); eShEntSize = restBuffer.getShort(); eShNum = restBuffer.getShort(); eShStrNdx = restBuffer.getShort(); } } public static class ProgramHeader { // Segment types. public static final int PT_NULL = 0; public static final int PT_LOAD = 1; public static final int PT_DYNAMIC = 2; public static final int PT_INTERP = 3; public static final int PT_NOTE = 4; public static final int PT_SHLIB = 5; public static final int PT_PHDR = 6; public static final int PT_LOPROC = 0x70000000; public static final int PT_HIPROC = 0x7fffffff; // Segment flags. public static final int PF_R = 0x04; public static final int PF_W = 0x02; public static final int PF_X = 0x01; public final int pType; public final int pFlags; public final long pOffset; public final long pVddr; public final long pPddr; public final long pFileSize; public final long pMemSize; public final long pAlign; private ProgramHeader(ByteBuffer buffer, int elfClass) throws IOException { switch (elfClass) { case ElfHeader.ELFCLASS32: pType = buffer.getInt(); pOffset = buffer.getInt(); pVddr = buffer.getInt(); pPddr = buffer.getInt(); pFileSize = buffer.getInt(); pMemSize = buffer.getInt(); pFlags = buffer.getInt(); pAlign = buffer.getInt(); break; case ElfHeader.ELFCLASS64: pType = buffer.getInt(); pFlags = buffer.getInt(); pOffset = buffer.getLong(); pVddr = buffer.getLong(); pPddr = buffer.getLong(); pFileSize = buffer.getLong(); pMemSize = buffer.getLong(); pAlign = buffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + elfClass); } } } public static class SectionHeader { // Special section indexes. public static final int SHN_UNDEF = 0; public static final int SHN_LORESERVE = 0xff00; public static final int SHN_LOPROC = 0xff00; public static final int SHN_HIPROC = 0xff1f; public static final int SHN_ABS = 0xfff1; public static final int SHN_COMMON = 0xfff2; public static final int SHN_HIRESERVE = 0xffff; // Section types. public static final int SHT_NULL = 0; public static final int SHT_PROGBITS = 1; public static final int SHT_SYMTAB = 2; public static final int SHT_STRTAB = 3; public static final int SHT_RELA = 4; public static final int SHT_HASH = 5; public static final int SHT_DYNAMIC = 6; public static final int SHT_NOTE = 7; public static final int SHT_NOBITS = 8; public static final int SHT_REL = 9; public static final int SHT_SHLIB = 10; public static final int SHT_DYNSYM = 11; public static final int SHT_LOPROC = 0x70000000; public static final int SHT_HIPROC = 0x7fffffff; public static final int SHT_LOUSER = 0x80000000; public static final int SHT_HIUSER = 0xffffffff; // Section flags. public static final int SHF_WRITE = 0x1; public static final int SHF_ALLOC = 0x2; public static final int SHF_EXECINSTR = 0x4; public static final int SHF_MASKPROC = 0xf0000000; public final int shName; public final int shType; public final long shFlags; public final long shAddr; public final long shOffset; public final long shSize; public final int shLink; public final int shInfo; public final long shAddrAlign; public final long shEntSize; public String shNameStr; private SectionHeader(ByteBuffer buffer, int elfClass) throws IOException { switch (elfClass) { case ElfHeader.ELFCLASS32: shName = buffer.getInt(); shType = buffer.getInt(); shFlags = buffer.getInt(); shAddr = buffer.getInt(); shOffset = buffer.getInt(); shSize = buffer.getInt(); shLink = buffer.getInt(); shInfo = buffer.getInt(); shAddrAlign = buffer.getInt(); shEntSize = buffer.getInt(); break; case ElfHeader.ELFCLASS64: shName = buffer.getInt(); shType = buffer.getInt(); shFlags = buffer.getLong(); shAddr = buffer.getLong(); shOffset = buffer.getLong(); shSize = buffer.getLong(); shLink = buffer.getInt(); shInfo = buffer.getInt(); shAddrAlign = buffer.getLong(); shEntSize = buffer.getLong(); break; default: throw new IOException("Unexpected elf class: " + elfClass); } shNameStr = null; } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareFileLockHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileLock; /** * Created by zhangshaowen on 16/6/3. */ public class ShareFileLockHelper implements Closeable { public static final int MAX_LOCK_ATTEMPTS = 3; public static final int LOCK_WAIT_EACH_TIME = 10; private static final String TAG = "Tinker.FileLockHelper"; private final FileOutputStream outputStream; private final FileLock fileLock; private ShareFileLockHelper(File lockFile) throws IOException { outputStream = new FileOutputStream(lockFile); int numAttempts = 0; boolean isGetLockSuccess; FileLock localFileLock = null; //just wait twice, Exception saveException = null; while (numAttempts < MAX_LOCK_ATTEMPTS) { numAttempts++; try { localFileLock = outputStream.getChannel().lock(); isGetLockSuccess = (localFileLock != null); if (isGetLockSuccess) { break; } } catch (Exception e) { saveException = e; ShareTinkerLog.e(TAG, "getInfoLock Thread failed time:" + LOCK_WAIT_EACH_TIME); } //it can just sleep 0, afraid of cpu scheduling try { Thread.sleep(LOCK_WAIT_EACH_TIME); } catch (Exception ignore) { ShareTinkerLog.e(TAG, "getInfoLock Thread sleep exception", ignore); } } if (localFileLock == null) { throw new IOException("Tinker Exception:FileLockHelper lock file failed: " + lockFile.getAbsolutePath(), saveException); } fileLock = localFileLock; } public static ShareFileLockHelper getFileLock(File lockFile) throws IOException { return new ShareFileLockHelper(lockFile); } @Override public void close() throws IOException { try { if (fileLock != null) { fileLock.release(); } } finally { if (outputStream != null) { outputStream.close(); } } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareIntentUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.content.Intent; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; /** * Created by zhangshaowen on 16/3/18. */ public class ShareIntentUtil { //intent public static final String INTENT_RETURN_CODE = "intent_return_code"; public static final String INTENT_PATCH_OLD_VERSION = "intent_patch_old_version"; public static final String INTENT_PATCH_NEW_VERSION = "intent_patch_new_version"; public static final String INTENT_IS_PROTECTED_APP = "intent_is_protected_app"; public static final String INTENT_PATCH_MISMATCH_DEX_PATH = "intent_patch_mismatch_dex_path"; public static final String INTENT_PATCH_MISSING_DEX_PATH = "intent_patch_missing_dex_path"; public static final String INTENT_PATCH_DEXES_PATH = "intent_patch_dexes_path"; public static final String INTENT_PATCH_MISMATCH_LIB_PATH = "intent_patch_mismatch_lib_path"; public static final String INTENT_PATCH_MISSING_LIB_PATH = "intent_patch_missing_lib_path"; public static final String INTENT_PATCH_LIBS_PATH = "intent_patch_libs_path"; public static final String INTENT_PATCH_COST_TIME = "intent_patch_cost_time"; public static final String INTENT_PATCH_EXCEPTION = "intent_patch_exception"; public static final String INTENT_PATCH_PACKAGE_PATCH_CHECK = "intent_patch_package_patch_check"; public static final String INTENT_PATCH_PACKAGE_CONFIG = "intent_patch_package_config"; public static final String INTENT_PATCH_SYSTEM_OTA = "intent_patch_system_ota"; public static final String INTENT_PATCH_OAT_DIR = "intent_patch_oat_dir"; public static final String INTENT_PATCH_INTERPRET_EXCEPTION = "intent_patch_interpret_exception"; private static final String TAG = "ShareIntentUtil"; public static void setIntentReturnCode(Intent intent, int code) { intent.putExtra(INTENT_RETURN_CODE, code); } public static int getIntentReturnCode(Intent intent) { return getIntExtra(intent, INTENT_RETURN_CODE, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL); } public static void setIntentPatchCostTime(Intent intent, long cost) { intent.putExtra(INTENT_PATCH_COST_TIME, cost); } public static long getIntentPatchCostTime(Intent intent) { return intent.getLongExtra(INTENT_PATCH_COST_TIME, 0); } public static Throwable getIntentPatchException(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_EXCEPTION); if (serializable != null) { return (Throwable) serializable; } return null; } public static Throwable getIntentInterpretException(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_INTERPRET_EXCEPTION); if (serializable != null) { return (Throwable) serializable; } return null; } public static HashMap getIntentPatchDexPaths(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_DEXES_PATH); if (serializable != null) { return (HashMap) serializable; } return null; } public static HashMap getIntentPatchLibsPaths(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_LIBS_PATH); if (serializable != null) { return (HashMap) serializable; } return null; } public static HashMap getIntentPackageConfig(Intent intent) { Serializable serializable = getSerializableExtra(intent, INTENT_PATCH_PACKAGE_CONFIG); if (serializable != null) { return (HashMap) serializable; } return null; } public static ArrayList getStringArrayListExtra(Intent intent, String name) { if (null == intent) { return null; } ArrayList ret = null; try { ret = intent.getStringArrayListExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getStringExtra exception:" + e.getMessage()); ret = null; } return ret; } public static String getStringExtra(Intent intent, String name) { if (null == intent) { return null; } String ret = null; try { ret = intent.getStringExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getStringExtra exception:" + e.getMessage()); ret = null; } return ret; } public static Serializable getSerializableExtra(Intent intent, String name) { if (null == intent) { return null; } Serializable ret = null; try { ret = intent.getSerializableExtra(name); } catch (Exception e) { ShareTinkerLog.e(TAG, "getSerializableExtra exception:" + e.getMessage()); ret = null; } return ret; } public static int getIntExtra(Intent intent, String name, int defaultValue) { if (null == intent) { return defaultValue; } int ret = defaultValue; try { ret = intent.getIntExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getIntExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static boolean getBooleanExtra(Intent intent, String name, boolean defaultValue) { if (null == intent) { return defaultValue; } boolean ret = defaultValue; try { ret = intent.getBooleanExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getBooleanExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static long getLongExtra(Intent intent, String name, long defaultValue) { if (null == intent) { return defaultValue; } long ret = defaultValue; try { ret = intent.getLongExtra(name, defaultValue); } catch (Exception e) { ShareTinkerLog.e(TAG, "getIntExtra exception:" + e.getMessage()); ret = defaultValue; } return ret; } public static void fixIntentClassLoader(Intent intent, ClassLoader cl) { try { intent.setExtrasClassLoader(cl); } catch (Throwable thr) { thr.printStackTrace(); } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; /** * Created by tangyinsheng on 2017/3/14. */ public final class ShareOatUtil { private static final String TAG = "Tinker.OatUtil"; private ShareOatUtil() { throw new UnsupportedOperationException(); } /** * Get instruction set used to generate {@code oatFile}. * * @param oatFile * the oat file. * @return * the instruction used to generate this oat file, if the oat file does not * contain this value, an empty string will be returned. * * @throws IOException * If anything wrong when parsing the elf format or locating target field in oat header. */ public static String getOatFileInstructionSet(File oatFile) throws Throwable { ShareElfFile elfFile = null; String result = ""; try { elfFile = new ShareElfFile(oatFile); final ShareElfFile.SectionHeader roDataHdr = elfFile.getSectionHeaderByName(".rodata"); if (roDataHdr == null) { throw new IOException("Unable to find .rodata section."); } final FileChannel channel = elfFile.getChannel(); channel.position(roDataHdr.shOffset); final byte[] oatMagicAndVersion = new byte[8]; ShareElfFile.readUntilLimit(channel, ByteBuffer.wrap(oatMagicAndVersion), "Failed to read oat magic and version."); if (oatMagicAndVersion[0] != 'o' || oatMagicAndVersion[1] != 'a' || oatMagicAndVersion[2] != 't' || oatMagicAndVersion[3] != '\n') { throw new IOException( String.format("Bad oat magic: %x %x %x %x", oatMagicAndVersion[0], oatMagicAndVersion[1], oatMagicAndVersion[2], oatMagicAndVersion[3]) ); } final int versionOffsetFromOatBegin = 4; final int versionBytes = 3; final String oatVersion = new String(oatMagicAndVersion, versionOffsetFromOatBegin, versionBytes, Charset.forName("ASCII")); try { Integer.parseInt(oatVersion); } catch (NumberFormatException e) { throw new IOException("Bad oat version: " + oatVersion); } ByteBuffer buffer = ByteBuffer.allocate(128); buffer.order(elfFile.getDataOrder()); // TODO This is a risk point, since each oat version may use a different offset. // So far it's ok. Perhaps we should use oatVersionNum to judge the right offset in // the future. final int isaNumOffsetFromOatBegin = 12; channel.position(roDataHdr.shOffset + isaNumOffsetFromOatBegin); buffer.limit(4); ShareElfFile.readUntilLimit(channel, buffer, "Failed to read isa num."); int isaNum = buffer.getInt(); if (isaNum < 0 || isaNum >= InstructionSet.values().length) { throw new IOException("Bad isa num: " + isaNum); } switch (InstructionSet.values()[isaNum]) { case kArm: case kThumb2: result = "arm"; break; case kArm64: result = "arm64"; break; case kX86: result = "x86"; break; case kX86_64: result = "x86_64"; break; case kMips: result = "mips"; break; case kMips64: result = "mips64"; break; case kNone: result = "none"; break; default: throw new IOException("Should not reach here."); } } finally { if (elfFile != null) { try { elfFile.close(); } catch (Exception ignored) { // Ignored. } } } return result; } private enum InstructionSet { kNone, kArm, kArm64, kThumb2, kX86, kX86_64, kMips, kMips64 } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchFileUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.MessageDigest; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class SharePatchFileUtil { private static final String TAG = "Tinker.PatchFileUtil"; private static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /** * data dir, such as /data/data/tinker.sample.android/tinker * @param context * @return */ public static File getPatchDirectory(Context context) { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. return null; } final String dirName = ("oppo".equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT == 22) ? ShareConstants.PATCH_DIRECTORY_NAME_SPEC : ShareConstants.PATCH_DIRECTORY_NAME; return new File(applicationInfo.dataDir, dirName); } public static File getPatchTempDirectory(Context context) { ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo == null) { // Looks like running on a test Context, so just return without patching. return null; } return new File(applicationInfo.dataDir, ShareConstants.PATCH_TEMP_DIRECTORY_NAME); } public static File getPatchLastCrashFile(Context context) { File tempFile = getPatchTempDirectory(context); if (tempFile == null) { return null; } return new File(tempFile, ShareConstants.PATCH_TEMP_LAST_CRASH_NAME); } public static File getPatchInfoFile(String patchDirectory) { return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_NAME); } public static File getPatchInfoLockFile(String patchDirectory) { return new File(patchDirectory + "/" + ShareConstants.PATCH_INFO_LOCK_NAME); } public static String getPatchVersionDirectory(String version) { if (version == null || version.length() != ShareConstants.MD5_LENGTH) { return null; } return ShareConstants.PATCH_BASE_NAME + version.substring(0, 8); } public static String getPatchVersionFile(String version) { if (version == null || version.length() != ShareConstants.MD5_LENGTH) { return null; } return getPatchVersionDirectory(version) + ShareConstants.PATCH_SUFFIX; } public static boolean checkIfMd5Valid(final String object) { if ((object == null) || (object.length() != ShareConstants.MD5_LENGTH)) { return false; } return true; } public static String checkTinkerLastUncaughtCrash(Context context) { File crashFile = SharePatchFileUtil.getPatchLastCrashFile(context); if (!SharePatchFileUtil.isLegalFile(crashFile)) { return null; } StringBuffer buffer = new StringBuffer(); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(crashFile))); String line; while ((line = in.readLine()) != null) { buffer.append(line); buffer.append("\n"); } } catch (Exception e) { ShareTinkerLog.e(TAG, "checkTinkerLastUncaughtCrash exception: " + e); return null; } finally { closeQuietly(in); } return buffer.toString(); } /** * Closes the given {@code obj}. Suppresses any exceptions. */ @SuppressLint("NewApi") public static void closeQuietly(Object obj) { if (obj == null) return; if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (Throwable ignored) { // Ignored. } } else if (Build.VERSION.SDK_INT >= 19 && obj instanceof AutoCloseable) { try { ((AutoCloseable) obj).close(); } catch (Throwable ignored) { // Ignored. } } else if (obj instanceof ZipFile) { try { ((ZipFile) obj).close(); } catch (Throwable ignored) { // Ignored. } } else { throw new IllegalArgumentException("obj: " + obj + " cannot be closed."); } } public static final boolean isLegalFile(File file) { return file != null && file.exists() && file.canRead() && file.isFile() && file.length() > 0; } /** * For some special device whose dex2oat procedure is optimized for tinker. (e.g. vivo, oppo) * * Because these devices by-pass our dex2oat request, which cause vm to load tinker's dex with interpret-mode * and generate nothing instead of a valid oat file. It's fine to skip the check so far. * * @param file * @return */ public static final boolean shouldAcceptEvenIfIllegal(File file) { final boolean isSpecialManufacturer = "vivo".equalsIgnoreCase(Build.MANUFACTURER) || "oppo".equalsIgnoreCase(Build.MANUFACTURER); final boolean isSpecialOSVer = (Build.VERSION.SDK_INT >= 29) || (Build.VERSION.SDK_INT >= 28 && Build.VERSION.PREVIEW_SDK_INT != 0) || (ShareTinkerInternals.isArkHotRuning()); final boolean isFileIllegal = !file.exists() || file.length() == 0; return (isSpecialManufacturer || isSpecialOSVer) && isFileIllegal; } /** * get directory size * * @param directory * @return */ public static long getFileOrDirectorySize(File directory) { if (directory == null || !directory.exists()) { return 0; } if (directory.isFile()) { return directory.length(); } long totalSize = 0; File[] fileList = directory.listFiles(); if (fileList != null) { for (File file : fileList) { if (file.isDirectory()) { totalSize = totalSize + getFileOrDirectorySize(file); } else { totalSize = totalSize + file.length(); } } } return totalSize; } public static final boolean safeDeleteFile(File file) { if (file == null) { return true; } if (file.exists()) { ShareTinkerLog.i(TAG, "safeDeleteFile, try to delete path: " + file.getPath()); boolean deleted = file.delete(); if (!deleted) { ShareTinkerLog.e(TAG, "Failed to delete file, try to delete when exit. path: " + file.getPath()); file.deleteOnExit(); } return deleted; } return true; } public static final boolean deleteDir(String dir) { if (dir == null) { return false; } return deleteDir(new File(dir)); } public static final boolean deleteDir(File file) { if (file == null || (!file.exists())) { return false; } if (file.isFile()) { safeDeleteFile(file); } else if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { for (File subFile : files) { deleteDir(subFile); } safeDeleteFile(file); } } return true; } /** * Returns whether the file is a valid file. */ public static boolean verifyFileMd5(File file, String md5) { if (md5 == null) { return false; } String fileMd5 = getMD5(file); if (fileMd5 == null) { return false; } return md5.equals(fileMd5); } public static boolean isRawDexFile(String fileName) { if (fileName == null) { return false; } return fileName.endsWith(ShareConstants.DEX_SUFFIX); } /** * Returns whether the dex file is a valid file. * dex may wrap with jar */ public static boolean verifyDexFileMd5(File file, String md5) { return verifyDexFileMd5(file, ShareConstants.DEX_IN_JAR, md5); } public static boolean verifyDexFileMd5(File file, String entryName, String md5) { if (file == null || md5 == null || entryName == null) { return false; } //if it is not the raw dex, we check the stream instead String fileMd5 = ""; if (isRawDexFile(file.getName())) { fileMd5 = getMD5(file); } else { ZipFile dexJar = null; try { dexJar = new ZipFile(file); ZipEntry classesDex = dexJar.getEntry(entryName); // no code if (null == classesDex) { ShareTinkerLog.e(TAG, "There's no entry named: " + ShareConstants.DEX_IN_JAR + " in " + file.getAbsolutePath()); return false; } InputStream is = null; try { is = dexJar.getInputStream(classesDex); fileMd5 = getMD5(is); } catch (Throwable e) { ShareTinkerLog.e(TAG, "exception occurred when get md5: " + file.getAbsolutePath(), e); } finally { closeQuietly(is); } } catch (Throwable e) { ShareTinkerLog.e(TAG, "Bad dex jar file: " + file.getAbsolutePath(), e); return false; } finally { closeZip(dexJar); } } return md5.equals(fileMd5); } public static void copyFileUsingStream(File source, File dest) throws IOException { if (!SharePatchFileUtil.isLegalFile(source) || dest == null) { return; } if (source.getAbsolutePath().equals(dest.getAbsolutePath())) { return; } FileInputStream is = null; FileOutputStream os = null; File parent = dest.getParentFile(); if (parent != null && (!parent.exists())) { parent.mkdirs(); } try { is = new FileInputStream(source); os = new FileOutputStream(dest, false); byte[] buffer = new byte[ShareConstants.BUFFER_SIZE]; int length; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } } finally { closeQuietly(is); closeQuietly(os); } } /** * for faster, read and get the contents * * @throws IOException */ public static String loadDigestes(JarFile jarFile, JarEntry je) throws Exception { InputStream bis = null; StringBuilder sb = new StringBuilder(); try { InputStream is = jarFile.getInputStream(je); byte[] bytes = new byte[ShareConstants.BUFFER_SIZE]; bis = new BufferedInputStream(is); int readBytes; while ((readBytes = bis.read(bytes)) > 0) { sb.append(new String(bytes, 0, readBytes)); } } finally { closeQuietly(bis); } return sb.toString(); } /** * Get the md5 for inputStream. * This method cost less memory. It read bufLen bytes from the FileInputStream once. * * @param is */ public final static String getMD5(final InputStream is) { if (is == null) { return null; } try { BufferedInputStream bis = new BufferedInputStream(is); MessageDigest md = MessageDigest.getInstance("MD5"); StringBuilder md5Str = new StringBuilder(32); byte[] buf = new byte[ShareConstants.MD5_FILE_BUF_LENGTH]; int readCount; while ((readCount = bis.read(buf)) != -1) { md.update(buf, 0, readCount); } byte[] hashValue = md.digest(); for (int i = 0; i < hashValue.length; i++) { md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); } return md5Str.toString(); } catch (Exception e) { return null; } } public static String getMD5(byte[] buffer) { try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(buffer); byte[] md = mdTemp.digest(); int j = md.length; char[] str = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { return null; } } /** * Get the md5 for the file. call getMD5(FileInputStream is, int bufLen) inside. * * @param file */ public static String getMD5(final File file) { if (file == null || !file.exists()) { return null; } FileInputStream fin = null; try { fin = new FileInputStream(file); String md5 = getMD5(fin); return md5; } catch (Exception e) { ShareTinkerLog.e(TAG, e.getMessage()); return null; } finally { closeQuietly(fin); } } /** * change the jar file path as the makeDexElements do * Android O change its path * * @param path * @param optimizedDirectory * @return */ public static String optimizedPathFor(File path, File optimizedDirectory) { if (ShareTinkerInternals.isAfterAndroidO()) { // dex_location = /foo/bar/baz.jar // odex_location = /foo/bar/oat//baz.odex String currentInstructionSet; try { currentInstructionSet = ShareTinkerInternals.getCurrentInstructionSet(); } catch (Exception e) { throw new TinkerRuntimeException("getCurrentInstructionSet fail:", e); } File parentFile = path.getParentFile(); String fileName = path.getName(); int index = fileName.lastIndexOf('.'); if (index > 0) { fileName = fileName.substring(0, index); } String result = parentFile.getAbsolutePath() + "/oat/" + currentInstructionSet + "/" + fileName + ShareConstants.ODEX_SUFFIX; return result; } String fileName = path.getName(); if (!fileName.endsWith(ShareConstants.DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += ShareConstants.DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(ShareConstants.DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath(); } public static void closeZip(ZipFile zipFile) { try { if (zipFile != null) { zipFile.close(); } } catch (IOException e) { ShareTinkerLog.w(TAG, "Failed to close resource", e); } } public static boolean checkResourceArscMd5(File resOutput, String destMd5) { ZipFile resourceZip = null; try { resourceZip = new ZipFile(resOutput); ZipEntry arscEntry = resourceZip.getEntry(ShareConstants.RES_ARSC); if (arscEntry == null) { ShareTinkerLog.i(TAG, "checkResourceArscMd5 resources.arsc not found"); return false; } InputStream inputStream = null; try { inputStream = resourceZip.getInputStream(arscEntry); String md5 = SharePatchFileUtil.getMD5(inputStream); if (md5 != null && md5.equals(destMd5)) { return true; } } finally { closeQuietly(inputStream); } } catch (Throwable e) { ShareTinkerLog.i(TAG, "checkResourceArscMd5 throwable:" + e.getMessage()); } finally { SharePatchFileUtil.closeZip(resourceZip); } return false; } public static void ensureFileDirectory(File file) { if (file == null) { return; } File parentFile = file.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/SharePatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.os.Build; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; /** * Created by zhangshaowen on 16/3/16. */ public class SharePatchInfo { private static final String TAG = "Tinker.PatchInfo"; public static final int MAX_EXTRACT_ATTEMPTS = ShareConstants.MAX_EXTRACT_ATTEMPTS; public static final String OLD_VERSION = ShareConstants.OLD_VERSION; public static final String NEW_VERSION = ShareConstants.NEW_VERSION; public static final String IS_PROTECTED_APP = ShareConstants.PKGMETA_KEY_IS_PROTECTED_APP; public static final String IS_REMOVE_NEW_VERSION = "is_remove_new_version"; public static final String FINGER_PRINT = "print"; public static final String OAT_DIR = "dir"; public static final String IS_REMOVE_INTERPRET_OAT_DIR = "is_remove_interpret_oat_dir"; public static final String DEFAULT_DIR = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; public String oldVersion; public String newVersion; public boolean isProtectedApp; public boolean isRemoveNewVersion; public String fingerPrint; public String oatDir; public boolean isRemoveInterpretOATDir; public SharePatchInfo(String oldVer, String newVer, boolean isProtectedApp, boolean isRemoveNewVersion, String finger, String oatDir, boolean isRemoveInterpretOATDir) { // TODO Auto-generated constructor stub this.oldVersion = oldVer; this.newVersion = newVer; this.isProtectedApp = isProtectedApp; this.isRemoveNewVersion = isRemoveNewVersion; this.fingerPrint = finger; this.oatDir = oatDir; this.isRemoveInterpretOATDir = isRemoveInterpretOATDir; } public static SharePatchInfo readAndCheckPropertyWithLock(File pathInfoFile, File lockFile) { if (pathInfoFile == null || lockFile == null) { return null; } File lockParentFile = lockFile.getParentFile(); if (!lockParentFile.exists()) { lockParentFile.mkdirs(); } SharePatchInfo patchInfo; ShareFileLockHelper fileLock = null; try { fileLock = ShareFileLockHelper.getFileLock(lockFile); patchInfo = readAndCheckProperty(pathInfoFile); } catch (Exception e) { throw new TinkerRuntimeException("readAndCheckPropertyWithLock fail", e); } finally { try { if (fileLock != null) { fileLock.close(); } } catch (IOException e) { ShareTinkerLog.w(TAG, "releaseInfoLock error", e); } } return patchInfo; } public static boolean rewritePatchInfoFileWithLock(File pathInfoFile, SharePatchInfo info, File lockFile) { if (pathInfoFile == null || info == null || lockFile == null) { return false; } File lockParentFile = lockFile.getParentFile(); if (!lockParentFile.exists()) { lockParentFile.mkdirs(); } boolean rewriteSuccess; ShareFileLockHelper fileLock = null; try { fileLock = ShareFileLockHelper.getFileLock(lockFile); rewriteSuccess = rewritePatchInfoFile(pathInfoFile, info); } catch (Exception e) { throw new TinkerRuntimeException("rewritePatchInfoFileWithLock fail", e); } finally { try { if (fileLock != null) { fileLock.close(); } } catch (IOException e) { ShareTinkerLog.i(TAG, "releaseInfoLock error", e); } } return rewriteSuccess; } private static SharePatchInfo readAndCheckProperty(File pathInfoFile) { boolean isReadPatchSuccessful = false; int numAttempts = 0; String oldVer = null; String newVer = null; String lastFingerPrint = null; boolean isProtectedApp = false; boolean isRemoveNewVersion = false; String oatDir = null; boolean isRemoveInterpretOATDir = false; while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isReadPatchSuccessful) { numAttempts++; Properties properties = new Properties(); FileInputStream inputStream = null; try { inputStream = new FileInputStream(pathInfoFile); properties.load(inputStream); oldVer = properties.getProperty(OLD_VERSION); newVer = properties.getProperty(NEW_VERSION); final String isProtectedAppStr = properties.getProperty(IS_PROTECTED_APP); isProtectedApp = (isProtectedAppStr != null && !isProtectedAppStr.isEmpty() && !"0".equals(isProtectedAppStr)); final String isRemoveNewVersionStr = properties.getProperty(IS_REMOVE_NEW_VERSION); isRemoveNewVersion = (isRemoveNewVersionStr != null && !isRemoveNewVersionStr.isEmpty() && !"0".equals(isRemoveNewVersionStr)); lastFingerPrint = properties.getProperty(FINGER_PRINT); oatDir = properties.getProperty(OAT_DIR); final String isRemoveInterpretOATDirStr = properties.getProperty(IS_REMOVE_INTERPRET_OAT_DIR); isRemoveInterpretOATDir = (isRemoveInterpretOATDirStr != null && !isRemoveInterpretOATDirStr.isEmpty() && !"0".equals(isRemoveInterpretOATDirStr)); } catch (IOException e) { ShareTinkerLog.w(TAG, "read property failed, e:" + e); } finally { SharePatchFileUtil.closeQuietly(inputStream); } if (oldVer == null || newVer == null) { continue; } //oldVer may be "" or 32 md5 if ((!oldVer.equals("") && !SharePatchFileUtil.checkIfMd5Valid(oldVer)) || !SharePatchFileUtil.checkIfMd5Valid(newVer)) { ShareTinkerLog.w(TAG, "path info file corrupted:" + pathInfoFile.getAbsolutePath()); continue; } else { isReadPatchSuccessful = true; } } if (isReadPatchSuccessful) { return new SharePatchInfo(oldVer, newVer, isProtectedApp, isRemoveNewVersion, lastFingerPrint, oatDir, isRemoveInterpretOATDir); } return null; } private static boolean rewritePatchInfoFile(File pathInfoFile, SharePatchInfo info) { if (pathInfoFile == null || info == null) { return false; } // write fingerprint if it is null or nil if (ShareTinkerInternals.isNullOrNil(info.fingerPrint)) { info.fingerPrint = Build.FINGERPRINT; } if (ShareTinkerInternals.isNullOrNil(info.oatDir)) { info.oatDir = DEFAULT_DIR; } ShareTinkerLog.i(TAG, "rewritePatchInfoFile file path:" + pathInfoFile.getAbsolutePath() + " , oldVer:" + info.oldVersion + ", newVer:" + info.newVersion + ", isProtectedApp:" + (info.isProtectedApp ? 1 : 0) + ", isRemoveNewVersion:" + (info.isRemoveNewVersion ? 1 : 0) + ", fingerprint:" + info.fingerPrint + ", oatDir:" + info.oatDir + ", isRemoveInterpretOATDir:" + (info.isRemoveInterpretOATDir ? 1 : 0) ); boolean isWritePatchSuccessful = false; int numAttempts = 0; File parentFile = pathInfoFile.getParentFile(); if (!parentFile.exists()) { parentFile.mkdirs(); } while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isWritePatchSuccessful) { numAttempts++; Properties newProperties = new Properties(); newProperties.put(OLD_VERSION, info.oldVersion); newProperties.put(NEW_VERSION, info.newVersion); newProperties.put(IS_PROTECTED_APP, (info.isProtectedApp ? "1" : "0")); newProperties.put(IS_REMOVE_NEW_VERSION, (info.isRemoveNewVersion ? "1" : "0")); newProperties.put(FINGER_PRINT, info.fingerPrint); newProperties.put(OAT_DIR, info.oatDir); newProperties.put(IS_REMOVE_INTERPRET_OAT_DIR, (info.isRemoveInterpretOATDir ? "1" : "0")); FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(pathInfoFile, false); String comment = "from old version:" + info.oldVersion + " to new version:" + info.newVersion; newProperties.store(outputStream, comment); } catch (Exception e) { ShareTinkerLog.w(TAG, "write property failed, e:" + e); } finally { SharePatchFileUtil.closeQuietly(outputStream); } SharePatchInfo tempInfo = readAndCheckProperty(pathInfoFile); isWritePatchSuccessful = tempInfo != null && tempInfo.oldVersion.equals(info.oldVersion) && tempInfo.newVersion.equals(info.newVersion); if (!isWritePatchSuccessful) { pathInfoFile.delete(); } } return isWritePatchSuccessful; } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareReflectUtil.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.content.Context; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; /** * Created by zhangshaowen on 16/8/22. */ public class ShareReflectUtil { /** * Locates a given field anywhere in the class inheritance hierarchy. * * @param instance an object to search the field into. * @param name field name * @return a field object * @throws NoSuchFieldException if the field cannot be located */ public static Field findField(Object instance, String name) throws NoSuchFieldException { for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); } public static Field findField(Class originClazz, String name) throws NoSuchFieldException { for (Class clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + originClazz); } /** * Locates a given method anywhere in the class inheritance hierarchy. * * @param instance an object to search the method into. * @param name method name * @param parameterTypes method parameter types * @return a method object * @throws NoSuchMethodException if the method cannot be located */ public static Method findMethod(Object instance, String name, Class... parameterTypes) throws NoSuchMethodException { for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); } /** * Locates a given method anywhere in the class inheritance hierarchy. * * @param clazz a class to search the method into. * @param name method name * @param parameterTypes method parameter types * @return a method object * @throws NoSuchMethodException if the method cannot be located */ public static Method findMethod(Class clazz, String name, Class... parameterTypes) throws NoSuchMethodException { for (; clazz != null; clazz = clazz.getSuperclass()) { try { Method method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + clazz); } /** * Locates a given constructor anywhere in the class inheritance hierarchy. * * @param instance an object to search the constructor into. * @param parameterTypes constructor parameter types * @return a constructor object * @throws NoSuchMethodException if the constructor cannot be located */ public static Constructor findConstructor(Object instance, Class... parameterTypes) throws NoSuchMethodException { for (Class clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); if (!ctor.isAccessible()) { ctor.setAccessible(true); } return ctor; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Constructor" + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); } /** * Replace the value of a field containing a non null array, by a new array containing the * elements of the original array plus the elements of extraElements. * * @param instance the instance whose field is to be modified. * @param fieldName the field to modify. * @param extraElements elements to append at the end of the array. */ public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) jlrField.get(instance); Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length); // NOTE: changed to copy extraElements first, for patch load first System.arraycopy(extraElements, 0, combined, 0, extraElements.length); System.arraycopy(original, 0, combined, extraElements.length, original.length); jlrField.set(instance, combined); } /** * Replace the value of a field containing a non null array, by a new array containing the * elements of the original array plus the elements of extraElements. * * @param instance the instance whose field is to be modified. * @param fieldName the field to modify. */ public static void reduceFieldArray(Object instance, String fieldName, int reduceSize) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { if (reduceSize <= 0) { return; } Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) jlrField.get(instance); int finalLength = original.length - reduceSize; if (finalLength <= 0) { return; } Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength); System.arraycopy(original, reduceSize, combined, 0, finalLength); jlrField.set(instance, combined); } public static Object getActivityThread(Context context, Class activityThread) { try { if (activityThread == null) { activityThread = Class.forName("android.app.ActivityThread"); } Method m = activityThread.getMethod("currentActivityThread"); m.setAccessible(true); Object currentActivityThread = m.invoke(null); if (currentActivityThread == null && context != null) { // In older versions of Android (prior to frameworks/base 66a017b63461a22842) // the currentActivityThread was built on thread locals, so we'll need to try // even harder Field mLoadedApk = context.getClass().getField("mLoadedApk"); mLoadedApk.setAccessible(true); Object apk = mLoadedApk.get(context); Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread"); mActivityThreadField.setAccessible(true); currentActivityThread = mActivityThreadField.get(apk); } return currentActivityThread; } catch (Throwable ignore) { return null; } } /** * Handy method for fetching hidden integer constant value in system classes. * * @param clazz * @param fieldName * @return */ public static int getValueOfStaticIntField(Class clazz, String fieldName, int defVal) { try { final Field field = findField(clazz, fieldName); return field.getInt(null); } catch (Throwable thr) { return defVal; } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareResPatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.regex.Pattern; /** * Created by zhangshaowen on 16/8/9. */ public class ShareResPatchInfo { public String arscBaseCrc = null; public String resArscMd5 = null; public ArrayList addRes = new ArrayList<>(); public ArrayList deleteRes = new ArrayList<>(); public ArrayList modRes = new ArrayList<>(); public HashMap storeRes = new HashMap<>(); //use linkHashMap instead? public ArrayList largeModRes = new ArrayList<>(); public HashMap largeModMap = new HashMap<>(); public HashSet patterns = new HashSet<>(); public static void parseAllResPatchInfo(String meta, ShareResPatchInfo info) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); for (int i = 0; i < lines.length; i++) { String line = lines[i]; if (line == null || line.length() <= 0) { continue; } if (line.startsWith(ShareConstants.RES_TITLE)) { final String[] kv = line.split(",", 3); info.arscBaseCrc = kv[1]; info.resArscMd5 = kv[2]; } else if (line.startsWith(ShareConstants.RES_PATTERN_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.patterns.add(convertToPatternString(lines[i + 1])); i++; } } else if (line.startsWith(ShareConstants.RES_ADD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.addRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_MOD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.modRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_LARGE_MOD_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { String nextLine = lines[i + 1]; final String[] data = nextLine.split(",", 3); String name = data[0]; LargeModeInfo largeModeInfo = new LargeModeInfo(); largeModeInfo.md5 = data[1]; largeModeInfo.crc = Long.parseLong(data[2]); info.largeModRes.add(name); info.largeModMap.put(name, largeModeInfo); i++; } } else if (line.startsWith(ShareConstants.RES_DEL_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.deleteRes.add(lines[i + 1]); i++; } } else if (line.startsWith(ShareConstants.RES_STORE_TITLE)) { final String[] kv = line.split(":", 2); int size = Integer.parseInt(kv[1]); for (; size > 0; size--) { info.storeRes.put(lines[i + 1], null); i++; } } } } public static boolean checkFileInPattern(HashSet patterns, String key) { if (!patterns.isEmpty()) { for (Iterator it = patterns.iterator(); it.hasNext();) { Pattern p = it.next(); if (p.matcher(key).matches()) { return true; } } } return false; } public static boolean checkResPatchInfo(ShareResPatchInfo info) { if (info == null) { return false; } String md5 = info.resArscMd5; if (md5 == null || md5.length() != ShareConstants.MD5_LENGTH) { return false; } return true; } private static Pattern convertToPatternString(String input) { //convert \\. if (input.contains(".")) { input = input.replaceAll("\\.", "\\\\."); } //convert ?to . if (input.contains("?")) { input = input.replaceAll("\\?", "\\."); } //convert * to.* if (input.contains("*")) { input = input.replace("*", ".*"); } Pattern pattern = Pattern.compile(input); return pattern; } public static void parseResPatchInfoFirstLine(String meta, ShareResPatchInfo info) { if (meta == null || meta.length() == 0) { return; } String[] lines = meta.split("\n"); String firstLine = lines[0]; if (firstLine == null || firstLine.length() <= 0) { throw new TinkerRuntimeException("res meta Corrupted:" + meta); } final String[] kv = firstLine.split(",", 3); info.arscBaseCrc = kv[1]; info.resArscMd5 = kv[2]; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("resArscMd5:" + resArscMd5 + "\n"); sb.append("arscBaseCrc:" + arscBaseCrc + "\n"); for (Pattern pattern : patterns) { sb.append("pattern:" + pattern + "\n"); } for (String add : addRes) { sb.append("addedSet:" + add + "\n"); } for (String mod : modRes) { sb.append("modifiedSet:" + mod + "\n"); } for (String largeMod : largeModRes) { sb.append("largeModifiedSet:" + largeMod + "\n"); } for (String del : deleteRes) { sb.append("deletedSet:" + del + "\n"); } for (String store : storeRes.keySet()) { sb.append("storeSet:" + store + "\n"); } return sb.toString(); } public static class LargeModeInfo { public String md5 = null; public long crc; public File file = null; } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareSecurityCheck.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.tencent.tinker.loader.TinkerRuntimeException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.HashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Created by zhangshaowen on 16/3/10. */ public class ShareSecurityCheck { private static final String TAG = "Tinker.SecurityCheck"; /** * static to faster * public key */ private static String mPublicKeyMd5 = null; private final Context mContext; private final HashMap metaContentMap; private final HashMap packageProperties; public ShareSecurityCheck(Context context) { mContext = context; metaContentMap = new HashMap<>(); packageProperties = new HashMap<>(); if (mPublicKeyMd5 == null) { init(mContext); } } public HashMap getMetaContentMap() { return metaContentMap; } /** * Nullable * * @return HashMap */ public HashMap getPackagePropertiesIfPresent() { if (!packageProperties.isEmpty()) { return packageProperties; } String property = metaContentMap.get(ShareConstants.PACKAGE_META_FILE); if (property == null) { return null; } String[] lines = property.split("\n"); for (final String line : lines) { if (line == null || line.length() <= 0) { continue; } //it is comment if (line.startsWith("#")) { continue; } final String[] kv = line.split("=", 2); if (kv == null || kv.length < 2) { continue; } packageProperties.put(kv[0].trim(), kv[1].trim()); } return packageProperties; } public boolean verifyPatchMetaSignature(File path) { if (!SharePatchFileUtil.isLegalFile(path)) { return false; } JarFile jarFile = null; try { jarFile = new JarFile(path); final Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); // no code if (jarEntry == null) { continue; } final String name = jarEntry.getName(); if (name.startsWith("META-INF/")) { continue; } //for faster, only check the meta.txt files //we will check other files's md5 written in meta files if (!name.endsWith(ShareConstants.META_SUFFIX)) { continue; } metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry)); Certificate[] certs = jarEntry.getCertificates(); if (certs == null || !check(path, certs)) { return false; } } } catch (Exception e) { throw new TinkerRuntimeException( String.format("ShareSecurityCheck file %s, size %d verifyPatchMetaSignature fail", path.getAbsolutePath(), path.length()), e); } finally { try { if (jarFile != null) { jarFile.close(); } } catch (IOException e) { ShareTinkerLog.e(TAG, path.getAbsolutePath(), e); } } return true; } // verify the signature of the Apk private boolean check(File path, Certificate[] certs) { if (certs.length > 0) { for (int i = certs.length - 1; i >= 0; i--) { try { if (mPublicKeyMd5.equals(SharePatchFileUtil.getMD5(certs[i].getEncoded()))) { return true; } } catch (Exception e) { ShareTinkerLog.e(TAG, path.getAbsolutePath(), e); } } } return false; } @SuppressLint("PackageManagerGetSignatures") private void init(Context context) { ByteArrayInputStream stream = null; try { PackageManager pm = context.getPackageManager(); String packageName = context.getPackageName(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); mPublicKeyMd5 = SharePatchFileUtil.getMD5(packageInfo.signatures[0].toByteArray()); if (mPublicKeyMd5 == null) { throw new TinkerRuntimeException("get public key md5 is null"); } } catch (Exception e) { throw new TinkerRuntimeException("ShareSecurityCheck init public key fail", e); } finally { SharePatchFileUtil.closeQuietly(stream); } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerInternals.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.text.TextUtils; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 16/3/10. */ public class ShareTinkerInternals { private static final String TAG = "Tinker.TinkerInternals"; private static final boolean VM_IS_ART = isVmArt(System.getProperty("java.vm.version")); private static final boolean VM_IS_JIT = isVmJitInternal(); private static final String PATCH_PROCESS_NAME = ":patch"; private static Boolean isPatchProcess = null; private static Boolean isARKHotRunning = null; /** * or you may just hardcode them in your app */ private static final String[] processName = {null}; private static String tinkerID = null; private static String currentInstructionSet = null; public static boolean isVmArt() { return VM_IS_ART || Build.VERSION.SDK_INT >= 21; } public static boolean isVmJit() { return VM_IS_JIT && Build.VERSION.SDK_INT < 24; } public static boolean isArkHotRuning() { if (isARKHotRunning != null) { return isARKHotRunning; } isARKHotRunning = false; Class arkApplicationInfo = null; try { arkApplicationInfo = ClassLoader.getSystemClassLoader() .getParent().loadClass("com.huawei.ark.app.ArkApplicationInfo"); Method isRunningInArkHot = null; isRunningInArkHot = arkApplicationInfo.getDeclaredMethod("isRunningInArk"); isRunningInArkHot.setAccessible(true); isARKHotRunning = (Boolean) isRunningInArkHot.invoke(null); } catch (ClassNotFoundException e) { ShareTinkerLog.i(TAG, "class not found exception"); } catch (NoSuchMethodException e) { ShareTinkerLog.i(TAG, "no such method exception"); } catch (SecurityException e) { ShareTinkerLog.i(TAG, "security exception"); } catch (IllegalAccessException e) { ShareTinkerLog.i(TAG, "illegal access exception"); } catch (InvocationTargetException e) { ShareTinkerLog.i(TAG, "invocation target exception"); } catch (IllegalArgumentException e) { ShareTinkerLog.i(TAG, "illegal argument exception"); } return isARKHotRunning; } public static boolean isAfterAndroidO() { return Build.VERSION.SDK_INT > 25; } public static String getCurrentInstructionSet() { if (currentInstructionSet != null) { return currentInstructionSet; } try { Class clazz = Class.forName("dalvik.system.VMRuntime"); Method currentGet = clazz.getDeclaredMethod("getCurrentInstructionSet"); currentGet.setAccessible(true); currentInstructionSet = (String) currentGet.invoke(null); } catch (Throwable ignored) { switch (Build.CPU_ABI) { case "armeabi": case "armeabi-v7a": currentInstructionSet = "arm"; break; case "arm64-v8a": currentInstructionSet = "arm64"; break; case "x86": currentInstructionSet = "x86"; break; case "x86_64": currentInstructionSet = "x86_64"; break; case "mips": currentInstructionSet = "mips"; break; case "mips64": currentInstructionSet = "mips64"; break; default: throw new IllegalStateException("Unsupported abi: " + Build.CPU_ABI); } } ShareTinkerLog.d(TAG, "getCurrentInstructionSet:" + currentInstructionSet); return currentInstructionSet; } public static boolean isSystemOTA(String lastFingerPrint) { String currentFingerprint = Build.FINGERPRINT; if (TextUtils.isEmpty(lastFingerPrint) || TextUtils.isEmpty(currentFingerprint)) { ShareTinkerLog.d(TAG, "fingerprint empty:" + lastFingerPrint + ",current:" + currentFingerprint); return false; } else { if (lastFingerPrint.equals(currentFingerprint)) { ShareTinkerLog.d(TAG, "same fingerprint:" + currentFingerprint); return false; } else { ShareTinkerLog.d(TAG, "system OTA,fingerprint not equal:" + lastFingerPrint + "," + currentFingerprint); return true; } } } public static ShareDexDiffPatchInfo changeTestDexToClassN(ShareDexDiffPatchInfo rawDexInfo, int index) { if (rawDexInfo.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) { String newName; if (index != 1) { newName = "classes" + index + ".dex"; } else { newName = "classes.dex"; } return new ShareDexDiffPatchInfo(newName, rawDexInfo.path, rawDexInfo.destMd5InDvm, rawDexInfo.destMd5InArt, rawDexInfo.dexDiffMd5, rawDexInfo.oldDexCrC, rawDexInfo.newOrPatchedDexCrC, rawDexInfo.dexMode); } return null; } public static boolean isNullOrNil(final String object) { if ((object == null) || (object.length() <= 0)) { return true; } return false; } /** * thinker package check * * @param context * @param tinkerFlag * @param patchFile * @param securityCheck * @return */ public static int checkTinkerPackage(Context context, int tinkerFlag, File patchFile, ShareSecurityCheck securityCheck) { int returnCode = checkSignatureAndTinkerID(context, patchFile, securityCheck); if (returnCode == ShareConstants.ERROR_PACKAGE_CHECK_OK) { returnCode = checkPackageAndTinkerFlag(securityCheck, tinkerFlag); } return returnCode; } /** * check patch file signature and TINKER_ID * * @param context * @param patchFile * @param securityCheck * @return */ public static int checkSignatureAndTinkerID(Context context, File patchFile, ShareSecurityCheck securityCheck) { if (!securityCheck.verifyPatchMetaSignature(patchFile)) { return ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL; } String oldTinkerId = getManifestTinkerID(context); if (oldTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND; } HashMap properties = securityCheck.getPackagePropertiesIfPresent(); if (properties == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND; } String patchTinkerId = properties.get(ShareConstants.TINKER_ID); if (patchTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND; } if (!oldTinkerId.equals(patchTinkerId)) { ShareTinkerLog.e(TAG, "tinkerId is not equal, base is " + oldTinkerId + ", but patch is " + patchTinkerId); return ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } public static int checkPackageAndTinkerFlag(ShareSecurityCheck securityCheck, int tinkerFlag) { if (isTinkerEnabledAll(tinkerFlag)) { return ShareConstants.ERROR_PACKAGE_CHECK_OK; } HashMap metaContentMap = securityCheck.getMetaContentMap(); //check dex boolean dexEnable = isTinkerEnabledForDex(tinkerFlag); if (!dexEnable && metaContentMap.containsKey(ShareConstants.DEX_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check native library boolean nativeEnable = isTinkerEnabledForNativeLib(tinkerFlag); if (!nativeEnable && metaContentMap.containsKey(ShareConstants.SO_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check resource boolean resEnable = isTinkerEnabledForResource(tinkerFlag); if (!resEnable && metaContentMap.containsKey(ShareConstants.RES_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } /** * not like {@cod ShareSecurityCheck.getPackagePropertiesIfPresent} * we don't check Signatures or other files, we just get the package meta's properties directly * * @param patchFile * @return */ public static Properties fastGetPatchPackageMeta(File patchFile) { if (patchFile == null || !patchFile.isFile() || patchFile.length() == 0) { ShareTinkerLog.e(TAG, "patchFile is illegal"); return null; } ZipFile zipFile = null; try { zipFile = new ZipFile(patchFile); ZipEntry packageEntry = zipFile.getEntry(ShareConstants.PACKAGE_META_FILE); if (packageEntry == null) { ShareTinkerLog.e(TAG, "patch meta entry not found"); return null; } InputStream inputStream = null; try { inputStream = zipFile.getInputStream(packageEntry); Properties properties = new Properties(); properties.load(inputStream); return properties; } finally { SharePatchFileUtil.closeQuietly(inputStream); } } catch (IOException e) { ShareTinkerLog.e(TAG, "fastGetPatchPackageMeta exception:" + e.getMessage()); return null; } finally { SharePatchFileUtil.closeZip(zipFile); } } public static String getManifestTinkerID(Context context) { if (tinkerID != null) { return tinkerID; } try { ApplicationInfo appInfo = context.getPackageManager() .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); Object object = appInfo.metaData.get(ShareConstants.TINKER_ID); if (object != null) { tinkerID = String.valueOf(object); } else { tinkerID = null; } } catch (Exception e) { ShareTinkerLog.e(TAG, "getManifestTinkerID exception:" + e.getMessage()); return null; } return tinkerID; } public static boolean isTinkerEnabledForDex(int flag) { return (flag & ShareConstants.TINKER_DEX_MASK) != 0; } public static boolean isTinkerEnabledForNativeLib(int flag) { return (flag & ShareConstants.TINKER_NATIVE_LIBRARY_MASK) != 0; } public static boolean isTinkerEnabledForResource(int flag) { //FIXME:res flag depends dex flag return (flag & ShareConstants.TINKER_RESOURCE_MASK) != 0; } public static boolean isTinkerEnabledForArkHot(int flag) { return (flag & ShareConstants.TINKER_ARKHOT_MASK) != 0; } public static String getTypeString(int type) { switch (type) { case ShareConstants.TYPE_DEX: return "dex"; case ShareConstants.TYPE_DEX_OPT: return "dex_opt"; case ShareConstants.TYPE_LIBRARY: return "lib"; case ShareConstants.TYPE_PATCH_FILE: return "patch_file"; case ShareConstants.TYPE_PATCH_INFO: return "patch_info"; case ShareConstants.TYPE_RESOURCE: return "resource"; default: return "unknown"; } } /** * you can set Tinker disable in runtime at some times! * * @param context */ public static void setTinkerDisableWithSharedPreferences(Context context) { SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); String keyName = getTinkerSwitchSPKey(context); sp.edit().putBoolean(keyName, false).commit(); } /** * can't load or receive any patch! * * @param context * @return */ public static boolean isTinkerEnableWithSharedPreferences(Context context) { if (context == null) { return false; } SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); String keyName = getTinkerSwitchSPKey(context); return sp.getBoolean(keyName, true); } private static String getTinkerSwitchSPKey(Context context) { String tmpTinkerId = getManifestTinkerID(context); if (isNullOrNil(tmpTinkerId)) { tmpTinkerId = "@@"; } return ShareConstants.TINKER_ENABLE_CONFIG_PREFIX + ShareConstants.TINKER_VERSION + "_" + tmpTinkerId; } public static int getSafeModeCount(Context context) { String processName = ShareTinkerInternals.getProcessName(context); String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG_PREFIX + processName; SharedPreferences sp = context.getSharedPreferences(preferName, Context.MODE_PRIVATE); int count = sp.getInt(ShareConstants.TINKER_SAFE_MODE_COUNT_PREFIX + ShareConstants.TINKER_VERSION, 0); ShareTinkerLog.w(TAG, "getSafeModeCount: preferName:" + preferName + " count:" + count); return count; } public static void setSafeModeCount(Context context, int count) { String processName = ShareTinkerInternals.getProcessName(context); String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG_PREFIX + processName; SharedPreferences sp = context.getSharedPreferences(preferName, Context.MODE_PRIVATE); sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT_PREFIX + ShareConstants.TINKER_VERSION, count).commit(); ShareTinkerLog.w(TAG, "setSafeModeCount: preferName:" + preferName + " count:" + count); } public static boolean isTinkerEnabled(int flag) { return (flag != ShareConstants.TINKER_DISABLE); } public static boolean isTinkerEnabledAll(int flag) { return (flag == ShareConstants.TINKER_ENABLE_ALL); } public static boolean isInMainProcess(Context context) { String mainProcessName = null; ApplicationInfo applicationInfo = context.getApplicationInfo(); if (applicationInfo != null) { mainProcessName = applicationInfo.processName; } if (isNullOrNil(mainProcessName)) { mainProcessName = context.getPackageName(); } String processName = getProcessName(context); if (processName == null || processName.length() == 0) { processName = ""; } return mainProcessName.equals(processName); } public static boolean isInPatchProcess(Context context) { if (isPatchProcess != null) { return isPatchProcess; } isPatchProcess = getProcessName(context).endsWith(PATCH_PROCESS_NAME); return isPatchProcess; } public static String getCurrentOatMode(Context context, String current) { if (current.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH)) { if (isInMainProcess(context)) { current = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; } else { current = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH; } } return current; } public static void killAllOtherProcess(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List appProcessList = am .getRunningAppProcesses(); if (appProcessList == null) { return; } // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M // BUT THAT'S ENOUGH HERE for (ActivityManager.RunningAppProcessInfo ai : appProcessList) { // KILL OTHER PROCESS OF MINE if (ai.uid == android.os.Process.myUid() && ai.pid != android.os.Process.myPid()) { android.os.Process.killProcess(ai.pid); } } } public static void killProcessExceptMain(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List appProcessList = am.getRunningAppProcesses(); if (appProcessList != null) { // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M // BUT THAT'S ENOUGH HERE for (ActivityManager.RunningAppProcessInfo ai : appProcessList) { if (ai.uid != android.os.Process.myUid()) { continue; } if (ai.processName.equals(context.getPackageName())) { continue; } android.os.Process.killProcess(ai.pid); } } } /** * add process name cache * * @param context * @return */ public static String getProcessName(Context context) { if (processName[0] == null) { synchronized (processName) { if (processName[0] == null) { processName[0] = getProcessNameInternal(context); } } } return (processName[0] != null ? processName[0] : ""); } @SuppressLint("NewApi") private static String getProcessNameInternal(final Context context) { if (isNewerOrEqualThanVersion(28, true)) { final String result = Application.getProcessName(); if (!TextUtils.isEmpty(result)) { return result; } } // The 'currentProcess' method only exists on api 18 and newer systems. if (isNewerOrEqualThanVersion(18, true)) { try { final Class activityThreadClazz = Class.forName("android.app.ActivityThread"); final Method currentProcessMethod = ShareReflectUtil.findMethod(activityThreadClazz, "currentProcessName"); currentProcessMethod.setAccessible(true); final String result = (String) currentProcessMethod.invoke(null); if (result != null && !result.isEmpty()) { return result; } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal reflect activity thread exception:" + thr.getMessage()); } } BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/cmdline"), StandardCharsets.US_ASCII)); String result = br.readLine(); if (result != null) { result = result.trim(); if (!result.isEmpty()) { return result; } } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal parse cmdline exception:" + thr.getMessage()); } finally { SharePatchFileUtil.closeQuietly(br); } if (context != null) { try { final int myPid = android.os.Process.myPid(); final int myUid = android.os.Process.myUid(); final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am != null) { final List procInfos = am.getRunningAppProcesses(); if (procInfos != null) { for (RunningAppProcessInfo procInfo : procInfos) { if (procInfo.pid == myPid && procInfo.uid == myUid) { return procInfo.processName; } } } } } catch (Throwable thr) { ShareTinkerLog.e(TAG, "getProcessNameInternal getRunningAppProcesses exception:" + thr.getMessage()); } } return null; } private static boolean isNewerOrEqualThanVersion(int apiLevel, boolean includePreviewVer) { if (includePreviewVer && Build.VERSION.SDK_INT >= 23) { return Build.VERSION.SDK_INT >= apiLevel || ((Build.VERSION.SDK_INT == apiLevel - 1) && Build.VERSION.PREVIEW_SDK_INT > 0); } else { return Build.VERSION.SDK_INT >= apiLevel; } } /** * vm whether it is art * * @return */ private static boolean isVmArt(String versionString) { boolean isArt = false; if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isArt = (major > 2) || ((major == 2) && (minor >= 1)); } catch (NumberFormatException e) { // let isMultidexCapable be false } } } return isArt; } private static boolean isVmJitInternal() { try { Class clazz = Class.forName("android.os.SystemProperties"); Method mthGet = clazz.getDeclaredMethod("get", String.class); String jit = (String) mthGet.invoke(null, "dalvik.vm.usejit"); String jitProfile = (String) mthGet.invoke(null, "dalvik.vm.usejitprofiles"); //usejit is true and usejitprofiles is null if (!isNullOrNil(jit) && isNullOrNil(jitProfile) && jit.equals("true")) { return true; } } catch (Throwable e) { ShareTinkerLog.e(TAG, "isVmJitInternal ex:" + e); } return false; } public static String getExceptionCauseString(final Throwable ex) { if (ex == null) return ""; final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(bos); try { // print directly Throwable t = ex; while (true) { Throwable cause = t.getCause(); if (cause == null) { break; } t = cause; } t.printStackTrace(ps); return toVisualString(bos.toString()); } finally { SharePatchFileUtil.closeQuietly(ps); } } public static String toVisualString(String src) { boolean cutFlg = false; if (null == src) { return null; } char[] chr = src.toCharArray(); if (null == chr) { return null; } int i = 0; for (; i < chr.length; i++) { if (chr[i] > 127) { chr[i] = 0; cutFlg = true; break; } } if (cutFlg) { return new String(chr, 0, i); } else { return src; } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareTinkerLog.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader.shareutil; import android.os.Handler; import android.os.Message; import android.util.Log; import java.lang.reflect.Constructor; /** * Created by zhangshaowen on 16/3/17. */ public class ShareTinkerLog { private static final String TAG = "Tinker.ShareTinkerLog"; public static final int FN_LOG_PRINT_STACKTRACE = 0xFA1; public static final int FN_LOG_PRINT_PENDING_LOGS = 0xFA2; private static final Handler[] tinkerLogInlineFenceRef = {null}; private static final TinkerLogImp debugLog = new TinkerLogImp() { @Override public void v(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); Log.v(tag, log); } @Override public void i(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); Log.i(tag, log); } @Override public void d(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); Log.d(tag, log); } @Override public void w(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); Log.w(tag, log); } @Override public void e(final String tag, final String format, final Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); Log.e(tag, log); } @Override public void printErrStackTrace(String tag, Throwable tr, String format, Object... params) { String log = (params == null || params.length == 0) ? format : String.format(format, params); if (log == null) { log = ""; } log += " " + Log.getStackTraceString(tr); Log.e(tag, log); } }; private static final TinkerLogImp[] tinkerLogImpRef = {debugLog}; static { synchronized (tinkerLogInlineFenceRef) { try { final Class clazz = Class.forName("com.tencent.tinker.loader.shareutil.TinkerLogInlineFence"); final Constructor ctor = clazz.getDeclaredConstructor(); ctor.setAccessible(true); tinkerLogInlineFenceRef[0] = (Handler) ctor.newInstance(); } catch (Throwable thr) { Log.e(TAG, "[-] Fail to create inline fence instance.", thr); tinkerLogInlineFenceRef[0] = null; } } } private static Handler getInlineFence() { synchronized (tinkerLogInlineFenceRef) { return tinkerLogInlineFenceRef[0]; } } public static TinkerLogImp getDefaultImpl() { return debugLog; } public static void setTinkerLogImp(TinkerLogImp imp) { synchronized (tinkerLogImpRef) { tinkerLogImpRef[0] = imp; if (imp != null && imp != debugLog) { printPendingLogs(); } } } public static TinkerLogImp getImpl() { synchronized (tinkerLogImpRef) { return tinkerLogImpRef[0]; } } public static void v(final String tag, final String fmt, final Object... values) { printLog(Log.VERBOSE, tag, fmt, values); } public static void d(final String tag, final String fmt, final Object... values) { printLog(Log.DEBUG, tag, fmt, values); } public static void i(final String tag, final String fmt, final Object... values) { printLog(Log.INFO, tag, fmt, values); } public static void w(final String tag, final String fmt, final Object... values) { printLog(Log.WARN, tag, fmt, values); } public static void e(final String tag, final String fmt, final Object... values) { printLog(Log.ERROR, tag, fmt, values); } public static void printErrStackTrace(String tag, Throwable thr, final String format, final Object... values) { printLog(tag, thr, format, values); } public static void printPendingLogs() { final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, FN_LOG_PRINT_PENDING_LOGS); inlineFence.handleMessage(msg); msg.recycle(); } } private static void printLog(int priority, String tag, String fmt, Object... values) { final long timestamp = System.currentTimeMillis(); final Object[] args = {priority, timestamp, tag, fmt, values}; final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, priority, args); inlineFence.handleMessage(msg); msg.recycle(); } else { debugLog.e(tag, "!! NO_LOG_IMPL !! Original Log: " + fmt, values); } } private static void printLog(String tag, Throwable thr, String fmt, Object... values) { final long timestamp = System.currentTimeMillis(); final Object[] args = {FN_LOG_PRINT_STACKTRACE, timestamp, tag, thr, fmt, values}; final Handler inlineFence = getInlineFence(); if (inlineFence != null) { final Message msg = Message.obtain(inlineFence, FN_LOG_PRINT_STACKTRACE, args); inlineFence.handleMessage(msg); msg.recycle(); } else { debugLog.printErrStackTrace(tag, thr, "!! NO_LOG_IMPL !! Original Log: " + fmt, values); } } public interface TinkerLogImp { void v(final String tag, final String fmt, final Object... values); void d(final String tag, final String fmt, final Object... values); void i(final String tag, final String fmt, final Object... values); void w(final String tag, final String fmt, final Object... values); void e(final String tag, final String fmt, final Object... values); void printErrStackTrace(String tag, Throwable tr, final String format, final Object... values); } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/TinkerLogInlineFence.java ================================================ package com.tencent.tinker.loader.shareutil; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.tencent.tinker.anno.Keep; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.FN_LOG_PRINT_PENDING_LOGS; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.FN_LOG_PRINT_STACKTRACE; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.getDefaultImpl; import static com.tencent.tinker.loader.shareutil.ShareTinkerLog.getImpl; /** * Created by tangyinsheng on 2020/6/4. */ @Keep final class TinkerLogInlineFence extends Handler { private static final String TAG = "Tinker.TinkerLogInlineFence"; private static final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); private static final List pendingLogs = new ArrayList<>(); @Override public void handleMessage(Message msg) { handleMessage_$noinline$(msg); } private void handleMessage_$noinline$(Message msg) { try { dummyThrowExceptionMethod(); } finally { handleMessageImpl(msg); } } private void handleMessageImpl(Message msg) { final ShareTinkerLog.TinkerLogImp defaultLogImp = getDefaultImpl(); final ShareTinkerLog.TinkerLogImp logImp = getImpl(); switch (msg.what) { case Log.VERBOSE: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.v((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.DEBUG: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.d((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.INFO: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.i((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.WARN: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.w((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case Log.ERROR: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.e((String) args[2], (String) args[3], (Object[]) args[4]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case FN_LOG_PRINT_STACKTRACE: { final Object[] args = (Object[]) msg.obj; if (logImp != null) { logImp.printErrStackTrace((String) args[2], (Throwable) args[3], (String) args[4], (Object[]) args[5]); } if (logImp == null || logImp == defaultLogImp) { synchronized (pendingLogs) { pendingLogs.add(args); } } break; } case FN_LOG_PRINT_PENDING_LOGS: { printPendingLogs(logImp); break; } default: { logImp.e(TAG, "[-] Bad msg id: " + msg.what); break; } } } private static void printPendingLogs(final ShareTinkerLog.TinkerLogImp logImp) { synchronized (pendingLogs) { if (logImp == null || pendingLogs.isEmpty()) { return; } } new Thread(new Runnable() { @Override public void run() { final SimpleDateFormat timestampFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH); synchronized (pendingLogs) { for (Object[] args : pendingLogs) { final Object[] argsRef = args; mainThreadHandler.post(new Runnable() { @Override public void run() { final String timestamp = timestampFmt.format(new Date((long) argsRef[1])); final String prefix = "[PendingLog @ " + timestamp + "] "; switch ((int) argsRef[0]) { case Log.VERBOSE: { logImp.v((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.DEBUG: { logImp.d((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.INFO: { logImp.i((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.WARN: { logImp.w((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case Log.ERROR: { logImp.e((String) argsRef[2], prefix + (String) argsRef[3], (Object[]) argsRef[4]); break; } case ShareTinkerLog.FN_LOG_PRINT_STACKTRACE: { logImp.printErrStackTrace((String) argsRef[2], (Throwable) argsRef[3], prefix + (String) argsRef[4], (Object[]) argsRef[5]); break; } default: { // Ignored. break; } } } }); } pendingLogs.clear(); } } }, "tinker_log_printer").start(); } private static void dummyThrowExceptionMethod() { if (TinkerLogInlineFence.class.isPrimitive()) { throw new RuntimeException(); } } } ================================================ FILE: tinker-android/tinker-android-loader-no-op/src/test/java/com/tencent/tinker/loader/ExampleUnitTest.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.loader; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: tinker-build/tinker-patch-cli/.gitignore ================================================ /build /tool_output/*.apk /tool_output/TinkerPatch ================================================ FILE: tinker-build/tinker-patch-cli/build.gradle ================================================ apply plugin: 'java-library' apply plugin: "com.github.johnrengelman.shadow" version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':tinker-build:tinker-patch-lib') } jar { manifest { attributes 'Main-Class': 'com.tencent.tinker.patch.CliMain' attributes 'Manifest-Version': version } } // copy the jar to work directory task buildTinkerSdk(type: Copy, dependsOn: [clean, shadowJar]) { group = "tinker" from('build/libs') { include '*.jar' exclude '*-javadoc.jar' exclude '*-sources.jar' } from('./tool_output') { include '*.*' } into(rootProject.file("buildSdk/build")) } defaultTasks 'buildTinkerSdk' ================================================ FILE: tinker-build/tinker-patch-cli/src/main/java/com/tencent/tinker/patch/CliMain.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.patch; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.patch.Runner; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import javax.xml.parsers.ParserConfigurationException; /** * Created by zhangshaowen on 2/27/16. * do not use Logger here */ public class CliMain extends Runner { private static final String ARG_HELP = "--help"; private static final String ARG_OUT = "-out"; private static final String ARG_CONFIG = "-config"; private static final String ARG_OLD = "-old"; private static final String ARG_NEW = "-new"; private static final String ARG_CUSTOM_PATH = "-customPath"; protected static String mRunningLocation; private CliMain(boolean isGradleMode) { super(isGradleMode); } public static void main(String[] args) { mBeginTime = System.currentTimeMillis(); CliMain m = new CliMain(false); setRunningLocation(m); m.run(args); } private static void setRunningLocation(CliMain m) { mRunningLocation = m.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); try { mRunningLocation = URLDecoder.decode(mRunningLocation, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (mRunningLocation.endsWith(".jar")) { mRunningLocation = mRunningLocation.substring(0, mRunningLocation.lastIndexOf(File.separator) + 1); } File f = new File(mRunningLocation); mRunningLocation = f.getAbsolutePath(); } private static void printUsage(PrintStream out) { // TODO: Look up launcher script name! String command = "tinker.jar"; //$NON-NLS-1$ out.println(); out.println(); out.println("Usage: java -jar " + command + " " + ARG_OLD + " old.apk " + ARG_NEW + " new.apk " + ARG_CONFIG + " tinker_config.xml " + ARG_OUT + " output_path " + ARG_CONFIG + " custom_file_cmd_path"); out.println("others please contact us"); } private void run(String[] args) { if (args.length < 1) { goToError(new IllegalArgumentException("Please provide required arguments."), ERRNO_USAGE); } try { ReadArgs readArgs = new ReadArgs(args).invoke(); File configFile = readArgs.getConfigFile(); File outputFile = readArgs.getOutputFile(); File oldApkFile = readArgs.getOldApkFile(); File newApkFile = readArgs.getNewApkFile(); String customDiffCmd = readArgs.getCustomDiffCmd(); if (oldApkFile == null || newApkFile == null) { goToError(new IllegalArgumentException("Missing old apk or new apk file argument"), ERRNO_ERRORS); } else if (!oldApkFile.exists() || !newApkFile.exists()) { goToError(new IOException("Old apk or new apk file does not exist"), ERRNO_ERRORS); } if (outputFile == null) { outputFile = new File(mRunningLocation, TypedValue.PATH_DEFAULT_OUTPUT); } loadConfigFromXml(configFile, outputFile, oldApkFile, newApkFile); if (customDiffCmd != null) { mConfig.mCustomDiffPath = customDiffCmd; } Logger.initLogger(mConfig); tinkerPatch(); } catch (IOException e) { goToError(e, ERRNO_ERRORS); } finally { Logger.closeLogger(); } } private void loadConfigFromXml(File configFile, File outputFile, File oldApkFile, File newApkFile) { if (configFile == null) { configFile = new File(mRunningLocation + File.separator + TypedValue.FILE_CONFIG); if (!configFile.exists()) { System.err.printf("the config file %s does not exit\n", configFile.getAbsolutePath()); printUsage(System.err); System.exit(ERRNO_USAGE); } } try { mConfig = new Configuration(configFile, outputFile, oldApkFile, newApkFile); } catch (IOException | ParserConfigurationException | SAXException e) { goToError(e, ERRNO_ERRORS); } catch (TinkerPatchException e) { goToError(e, ERRNO_ERRORS); } } private class ReadArgs { private String[] args; private File configFile; private File outputFile; private File oldApkFile; private File newApkFile; private String customDiffCmd; ReadArgs(String[] args) { this.args = args; } public File getConfigFile() { return configFile; } public File getOutputFile() { return outputFile; } public File getOldApkFile() { return oldApkFile; } public File getNewApkFile() { return newApkFile; } public String getCustomDiffCmd() { return customDiffCmd; } public ReadArgs invoke() { for (int index = 0; index < args.length; index++) { String arg = args[index]; if (arg.equals(ARG_HELP) || arg.equals("-h")) { printUsage(System.out); return this; } else if (arg.equals(ARG_CONFIG)) { if (index == args.length - 1 || !args[index + 1].endsWith(TypedValue.FILE_XML)) { goToError(new IllegalArgumentException("Missing XML configuration file argument"), ERRNO_USAGE); } configFile = new File(args[++index]); if (!configFile.exists()) { goToError(new IOException(configFile.getAbsolutePath() + " does not exist"), ERRNO_ERRORS); } System.out.println("special configFile file path:" + configFile.getAbsolutePath()); } else if (arg.equals(ARG_OUT)) { if (index == args.length - 1) { goToError(new IllegalArgumentException("Missing output file argument"), ERRNO_USAGE); } outputFile = new File(args[++index]); File parent = outputFile.getParentFile(); if (parent != null && (!parent.exists())) { parent.mkdirs(); } System.out.printf("special output directory path: %s\n", outputFile.getAbsolutePath()); } else if (arg.equals(ARG_OLD)) { if (index == args.length - 1) { goToError(new IllegalArgumentException("Missing old apk file argument"), ERRNO_USAGE); } oldApkFile = new File(args[++index]); } else if (arg.equals(ARG_NEW)) { if (index == args.length - 1) { goToError(new IllegalArgumentException("Missing new apk file argument"), ERRNO_USAGE); } newApkFile = new File(args[++index]); } else if (arg.equals(ARG_CUSTOM_PATH)) { if (index == args.length - 1) { goToError(new IllegalArgumentException("Missing output file argument"), ERRNO_USAGE); } customDiffCmd = args[++index]; System.out.printf("special output custom diff cmd: %s\n", customDiffCmd); } } return this; } } } ================================================ FILE: tinker-build/tinker-patch-cli/src/main/java/com/tencent/tinker/patch/Test.java ================================================ package com.tencent.tinker.patch; import com.tencent.tinker.build.dexpatcher.DexPatchGenerator; import com.tencent.tinker.build.util.DexClassesComparator; import com.tencent.tinker.commons.dexpatcher.DexPatchApplier; import java.io.File; public class Test { public static void main(String[] args) throws Throwable { final File oldDexFile = new File("/Users/tomystang/dexdiff_test/base.dex"); final File newDexFile = new File("/Users/tomystang/dexdiff_test/new.dex"); final File patchFile = new File("/Users/tomystang/dexdiff_test/patch.dexdiff"); final File patchedDexFile = new File("/Users/tomystang/dexdiff_test/new_patched.dex"); final DexPatchGenerator dg = new DexPatchGenerator(oldDexFile, newDexFile); dg.addAdditionalRemovingClassPattern("com.tencent.tinker.loader.*"); dg.executeAndSaveTo(patchFile); final DexPatchApplier da = new DexPatchApplier(oldDexFile, patchFile); da.executeAndSaveTo(patchedDexFile); final DexClassesComparator dcc = new DexClassesComparator("*"); dcc.setIgnoredRemovedClassDescPattern("com.tencent.tinker.loader.*"); dcc.setCompareMode(DexClassesComparator.COMPARE_MODE_NORMAL); dcc.startCheck(newDexFile, patchedDexFile); System.out.println("add classes: " + dcc.getAddedClassInfos()); System.out.println("del classes: " + dcc.getDeletedClassInfos()); System.out.println("changed classes: " + dcc.getChangedClassDescToInfosMap()); } } ================================================ FILE: tinker-build/tinker-patch-cli/tool_maple/build_patch_dexdiff.sh ================================================ #!/bin/bash helpme() { echo "Build patch zip" echo "USAGE:" echo " ./build_patch_dexdiff.sh [old=old.apk path] [new=new.apk path]" echo "EXAMPLE:" echo " ./build_patch_dexdiff.sh old=/old.apk new=/new.apk" exit 1 } parse_cmdline() { while [[ -n "$1" ]] do OPTIONS=`echo "$1" | sed 's/\(.*\)=\(.*\)/\1/'` PARAM=`echo "$1" | sed 's/.*=//'` if [[ "$1" != *=* ]];then helpme CHECK_FLAG=0 fi case "$OPTIONS" in old) OLD_APK_PATH=${PARAM};; new) NEW_APK_PATH=${PARAM};; #please add extra parameter here! *) if [[ `echo "$1" | sed -n "/.*=/p"` ]];then echo "Error, the pattem \"$OPTIONS=${PARAM}\" can not be recognized!!!" helpme fi break;; esac shift done COMMAND_ARGS=$@ } initParameter() { OLD_FILE=`pwd`/old_file NEW_FILE=`pwd`/new_file } dexdiff() { mkdir ${OLD_FILE} pushd ${OLD_FILE} unzip -o ${OLD_APK_PATH} -d ${OLD_FILE} OLD_DEX_COUNT=`ls | grep classes | wc -l` OLD_DEX_SET=${OLD_FILE}/classes.dex for count in `seq 2 ${OLD_DEX_COUNT}` do OLD_DEX_SET="${OLD_DEX_SET} ${OLD_FILE}/classes$count.dex" done popd mkdir ${NEW_FILE} pushd ${NEW_FILE} unzip -o ${NEW_APK_PATH} -d ${NEW_FILE} NEW_DEX_COUNT=`ls | grep classes | wc -l` NEW_DEX_SET=${NEW_FILE}/classes.dex for count in `seq 2 ${NEW_DEX_COUNT}` do NEW_DEX_SET="${NEW_DEX_SET} ${NEW_FILE}/classes$count.dex" done popd dexcmp -n ${NEW_DEX_SET} -o ${OLD_DEX_SET} -f `pwd`/patch.dex } dexcmp() { java -cp "./DexCmp.jar" "io.qivaz.dex.cmp.DexCmp" "$@" } zipPatch() { zip -m0 patch.apk ./patch.dex zip -m0 patch.apk ./new_file/classes.dex for count in `seq 2 ${NEW_DEX_COUNT}` do zip -m0 patch.apk ./new_file/classes${count}.dex done } parse_cmdline $@ initParameter dexdiff zipPatch ================================================ FILE: tinker-build/tinker-patch-cli/tool_output/merge_mapping.py ================================================ #!/usr/bin/python # coding: utf-8 """ 当工程使用了applymapping之后,会遇到这样的问题 1.类和方法上个版本被keep住了,这个版本不keep 2.类和方法上个版本没有被keep住,这个版本又keep住了 这两个问题会导致proguard报warning,官方建议是手动解决冲突 (http://proguard.sourceforge.net/manual/troubleshooting.html#mappingconflict1) 不解决的话默认以mapping文件为最高优先级处理,这样混淆会带来一些问题 该方案为 简单来说,上个版本的mapping称为mapping1,直接编译当前项目,获取当前的mapping文件,称为mapping2。 从mapping2中可以得到当前项目需要混淆的类,因为重用mapping的意义在于同样的类在不同版本中混淆后的名称保持一致性, 所以将mapping2里面的所有类和方法统统在mapping1中去查找对应的混淆名称,生成新的mapping3,找不到则不写入mapping3, 然后mapping3就是最后可以使用的mapping文件。最后重用mapping3来编译当前项目,完成打包整个过程 最后生成的mapping会保留当前版本和之前版本都需要混淆的类和方法,且混淆后的名字取之前的mapping版本中的名字, 对于keep状态冲突的类和方法,处理方式是不保留在新生成的mapping中,编译过程中由当前的proguard的配置文件来处理 so 新生成的mapping是之前版本mapping的一个子集 使用教程是传入上版本的mapping和当前项目未applymapping得到的mapping文件,输出处理后的mapping 文件。 """ import os import sys def print_usage(): print >>sys.stderr, \ """usage: python merge_mapping.py old_mapping.txt current_mapping.txt the output mapping file is 'new_mapping.txt' in the cwd directory """ sys.exit(1) class MappingData: def __init__(self): self.raw_line = "" self.key = "" self.field_methods = [] class DealWithProguardWarning: def __init__(self): self.classes = {} self.class_list = [] self.current_classes = {} self.current_class_list = [] @staticmethod def read_mapping_file(classes, class_list, mapping): current_mapping_data = None with open(mapping, 'r') as fd: # 一行一行读取 for line in fd.xreadlines(): # 如果不是空格开头,类的处理 if not line.startswith(' '): # 对象不为空,先保存之前的 if current_mapping_data is not None: classes[current_mapping_data.key] = current_mapping_data class_list.append(current_mapping_data.key) # 重新创建对象,并赋值 current_mapping_data = MappingData() current_mapping_data.raw_line = line current_mapping_data.key = line.split('->')[0].strip() else: # 方法的处理,直接加进去 current_mapping_data.field_methods.append(line) classes[current_mapping_data.key] = current_mapping_data class_list.append(current_mapping_data.key) print "size: ", len(classes) def remove_warning_mapping(self, old_mapping, current_mapping): self.read_mapping_file(self.classes, self.class_list, old_mapping) self.read_mapping_file(self.current_classes, self.current_class_list, current_mapping) self.do_merge() self.print_new_mapping() def exe(self, args): if len(args) < 2: print_usage() old_mapping_path = args[0] if not os.path.exists(old_mapping_path): raise Exception("mapping file is not exist, path=%s", old_mapping_path) current_mapping_path = args[1] if not os.path.exists(current_mapping_path): raise Exception("proguard warning file is not exist, path=%s", current_mapping_path) self.remove_warning_mapping(old_mapping_path, current_mapping_path) def do_merge(self): # 遍历当前的mapping class_key for key in self.current_class_list: if key in self.classes: data = self.classes[key] current_data = self.current_classes[key] # 如果当前的类没有被混淆,则保留,否则用之前的mapping里面的内容覆盖 # ___.___ -> ___.___: if current_data.raw_line.split("->")[0] != current_data.raw_line.split("->")[1][:-1]: current_data.raw_line = data.raw_line new_method_list = [] # 处理方法 for line in current_data.field_methods: result, new_line = self.find_same_methods(line, data) # 只有找到才写入 if result: new_method_list.append(new_line) current_data.field_methods = new_method_list # 新的混淆不在旧的里面,则删除 else: del self.current_classes[key] def find_same_methods(self, line, data): search_name, search_complete_name, search_new_name = self.get_name_and_complete_name_and_new_name(line) # 这里是特殊情况,如果在当前mapping发现查找的这个并没有混淆,就不打算保留在mapping文件中 if search_name == search_new_name: return False, "" for method_line in data.field_methods: target_name, target_complete_name, target_new_name = self.get_name_and_complete_name_and_new_name(method_line) # 这里必须要用最完整的信息来进行比较,避免重载的影响 if search_complete_name == target_complete_name: print "1" return True, method_line print "0" return False, "" # 返回名字 包含返回值和参数的名字 和 混淆后的名字 @staticmethod def get_name_and_complete_name_and_new_name(line): """ ___ ___ -> ___ ___:___:___ ___(___) -> ___ ___:___:___ ___(___):___ -> ___ ___:___:___ ___(___):___:___ -> ___ """ no_space_line = line.strip() colonIndex1 = no_space_line.find(":") colonIndex2 = no_space_line.find(":", colonIndex1+1) if colonIndex1 != -1 else -1 spaceIndex = no_space_line.find(" ", colonIndex2+2) argumentIndex1 = no_space_line.find("(", spaceIndex+1) argumentIndex2 = no_space_line.find(")", argumentIndex1+1) if argumentIndex1 != -1 else -1 colonIndex3 = no_space_line.find(":", argumentIndex2+1) if argumentIndex2 != -1 else -1 colonIndex4 = no_space_line.find(":", colonIndex3+1) if colonIndex3 != -1 else -1 arrowIndex = no_space_line.find("->") if spaceIndex < 0 or arrowIndex < 0: raise Exception("can not parse line %s", no_space_line) name = no_space_line[spaceIndex + 1: argumentIndex1 if argumentIndex1 >= 0 else arrowIndex].strip() new_name = no_space_line[arrowIndex + 2:].strip() complete_name = no_space_line[colonIndex2 + 1:arrowIndex].strip() return name, complete_name, new_name def print_new_mapping(self): output_path = os.path.join(os.getcwd(), "new_mapping.txt") with open(output_path, "w") as fw: for key in self.current_class_list: if key in self.current_classes: data = self.current_classes[key] fw.write(data.raw_line) for line in data.field_methods: fw.write(line) if __name__ == '__main__': DealWithProguardWarning().exe(sys.argv[1:]) ================================================ FILE: tinker-build/tinker-patch-cli/tool_output/proguard_warning.py ================================================ #!/usr/bin/python # coding: utf8 import os import sys def print_usage(): print >>sys.stderr, \ """usage: python proguard_warning.py mapping.txt warning.txt the output mapping file is 'mapping_edit.txt' in the cwd directory """ sys.exit(1) class MappingData: raw_line = "" key = "" filed_methods = [] def __init__(self): self.raw_line = "" self.key = "" self.filed_methods = [] class RemoveProguardWarning: def __init__(self): self.classes = {} self.class_list = [] def read_mapping_file(self, mapping): current_mapping_data = None with open(mapping) as fd: for line in fd.readlines(): if not line.startswith(' '): if current_mapping_data is not None: self.classes[current_mapping_data.key] = current_mapping_data self.class_list.append(current_mapping_data.key) current_mapping_data = MappingData() current_mapping_data.raw_line = line current_mapping_data.key = line.split('->')[0].strip() else: current_mapping_data.filed_methods.append(line) self.classes[current_mapping_data.key] = current_mapping_data self.class_list.append(current_mapping_data.key) # print "size: ", len(self.classes) def remove_warning(self, warning): with open(warning) as fd: for line in fd.readlines(): if not line.startswith("Warning:"): raise Exception("proguard warning must begin with 'Warning:', line=", line) splits = line.split(':') class_key = splits[1].strip() # print "class_key", class_key if class_key not in self.classes: print "Warning:can't find warning class in the mapping file, class=", class_key continue warning_value = splits[2].split("'")[1] + " -> " + splits[2].split("'")[5] mapping_data = self.classes[class_key] # print "warning_value", warning_value find = False for mappings in mapping_data.filed_methods: if mappings.find(warning_value) != -1: mapping_data.filed_methods.remove(mappings) find = True break if not find: print "Warning: can't find warning field or method in the mapping file:', value=", warning_value if len(mapping_data.filed_methods) == 0: del self.classes[class_key] output_path = os.path.join(os.getcwd(), "mapping_edit.txt") with open(output_path, "w") as fw: for key in self.class_list: if key in self.classes: data = self.classes[key] fw.write(data.raw_line) for line in data.filed_methods: fw.write(line) def remove_warning_mapping(self, mapping, warning): self.read_mapping_file(mapping) self.remove_warning(warning) def do_command(self, args): if (len(args) < 2): print_usage() mapping_path = args[0] if not os.path.exists(mapping_path): raise Exception("mapping file is not exist, path=%s", mapping_path) warning_patch = args[1] if not os.path.exists(warning_patch): raise Exception("proguard warning file is not exist, path=%s", warning_patch) self.remove_warning_mapping(mapping_path, warning_patch) if __name__ == '__main__': RemoveProguardWarning().do_command(sys.argv[1:]) ================================================ FILE: tinker-build/tinker-patch-cli/tool_output/tinker_config.xml ================================================ ================================================ FILE: tinker-build/tinker-patch-cli/tool_output/tinker_multidexkeep.pro ================================================ #tinker multidex keep patterns: -keep public class * implements com.tencent.tinker.entry.ApplicationLifeCycle { (); void onBaseContextAttached(android.content.Context); } -keep public class * implements com.tencent.tinker.loader.app.ITinkerInlineFenceBridge { (...); void attachBaseContext(com.tencent.tinker.loader.app.TinkerApplication, android.content.Context); } -keep public class * extends com.tencent.tinker.loader.TinkerLoader { (); } -keep public class * extends android.app.Application { (); void attachBaseContext(android.content.Context); } -keep public class com.tencent.tinker.loader.NewClassLoaderInjector { *; } -keep class com.tencent.tinker.loader.NewClassLoaderInjector$DispatchClassLoader { *; } -keep class com.tencent.tinker.entry.TinkerApplicationInlineFence { *; } -keep class com.tencent.tinker.loader.app.TinkerInlineFenceAction { *; } #your dex.loader patterns here -keep class tinker.sample.android.app.SampleApplication { (); } ================================================ FILE: tinker-build/tinker-patch-cli/tool_output/tinker_proguard.pro ================================================ #-applymapping "old apk mapping here" -keepattributes *Annotation* -dontwarn com.tencent.tinker.anno.AnnotationProcessor -keep @com.tencent.tinker.anno.DefaultLifeCycle public class * -keep public class * extends android.app.Application { *; } -keep public class com.tencent.tinker.entry.ApplicationLifeCycle { *; } -keep public class * implements com.tencent.tinker.entry.ApplicationLifeCycle { *; } -keep public class com.tencent.tinker.loader.TinkerLoader { *; } -keep public class * extends com.tencent.tinker.loader.TinkerLoader { *; } -keep public class com.tencent.tinker.loader.TinkerTestDexLoad { *; } -keep public class com.tencent.tinker.loader.TinkerTestDexLoad { *; } -keep public class com.tencent.tinker.entry.TinkerApplicationInlineFence { *; } #for command line version, we must keep all the loader class to avoid proguard mapping conflict #your dex.loader pattern here -keep public class com.tencent.tinker.loader.** { *; } -keep class tinker.sample.android.app.SampleApplication { *; } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/.gitignore ================================================ /build ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/build.gradle ================================================ apply plugin: 'groovy' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion dependencies { implementation gradleApi() implementation localGroovy() // implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':tinker-build:tinker-patch-lib') implementation 'com.google.gradle:osdetector-gradle-plugin:1.7.0' implementation 'commons-codec:commons-codec:1.10' compileOnly 'com.android.tools.build:gradle:4.2.0' } repositories { mavenCentral() } sourceSets { main { groovy { srcDir 'src/main/groovy' } resources { srcDir 'src/main/resources' } } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-patch-gradle-plugin POM_NAME=Tinker Patch Gradle Plugin POM_PACKAGING=jar ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/Compatibilities.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle import org.gradle.api.GradleException import org.gradle.api.Task import org.jetbrains.annotations.NotNull import java.lang.reflect.Field /** * For AGP Compatibilities * * @author tangyinsheng */ class Compatibilities { static def getApplicationId(project, variant) { return variant.getApplicationId() } static def getOutputManifestPath(project, manifestTask, variantOutput) { try { return new File(manifestTask.multiApkManifestOutputDirectory.get().asFile, "${variantOutput.dirName}/AndroidManifest.xml") } catch (Throwable ignored) { // Ignored. } try { return new File(manifestTask.manifestOutputDirectory.get().asFile, "${variantOutput.dirName}/AndroidManifest.xml") } catch (Throwable ignored) { // Ignored. } try { return new File(manifestTask.manifestOutputDirectory, "${variantOutput.dirName}/AndroidManifest.xml") } catch (Throwable ignored) { // Ignored } return manifestTask.manifestOutputFile } static def getInputResourcesDirectory(project, resourcesTask) { try { return resourcesTask.inputResourcesDir.getAsFile().get() } catch (Throwable ignored) { // Ignored. } try { return resourcesTask.inputResourcesDir.getFiles().first() } catch (Throwable ignored) { // Ignored. } return resourcesTask.resDir } static def getProcessManifestTask(project, variant) { return project.tasks.findByName("process${variant.name.capitalize()}Manifest") } static def getMergeResourcesTask(project, variant) { return project.tasks.findByName("merge${variant.name.capitalize()}Resources") } static def getProcessResourcesTask(project, variant) { return project.tasks.findByName("process${variant.name.capitalize()}Resources") } static def getAssembleTask(project, variant) { return project.tasks.findByName("assemble${variant.name.capitalize()}") } static def getMultiDexTask(project, variant) { def capitalizedVariantName = variant.name.capitalize() def multiDexTask = project.tasks.findByName("multiDexList${capitalizedVariantName}") if (multiDexTask != null) { return multiDexTask } return project.tasks.findByName("transformClassesWithMultidexlistFor${capitalizedVariantName}") } static def getR8Task(project, variant) { def capitalizedVariantName = variant.name.capitalize() def r8TransformTask = project.tasks.findByName("transformClassesAndResourcesWithR8For${capitalizedVariantName}") if (r8TransformTask != null) { return r8TransformTask } return project.tasks.findByName("minify${capitalizedVariantName}WithR8") } static def getObfuscateTask(project, variant) { def capitalizedVariantName = variant.name.capitalize() // For WeChat internal build tools. def customProguardTransformTask = project.tasks.findByName("transformClassesWithCustomProguardFor${capitalizedVariantName}") if (customProguardTransformTask != null && customProguardTransformTask.enabled) { return customProguardTransformTask } def proguardTransformTask = project.tasks.findByName("transformClassesAndResourcesWithProguardFor${capitalizedVariantName}") if (proguardTransformTask != null && proguardTransformTask.enabled) { return proguardTransformTask } def r8TransformTask = project.tasks.findByName("transformClassesAndResourcesWithR8For${capitalizedVariantName}") if (r8TransformTask != null && r8TransformTask.enabled) { return r8TransformTask } def r8Task = project.tasks.findByName("minify${capitalizedVariantName}WithR8") if (r8Task != null && r8Task.enabled) { return r8Task } def proguardTask = project.tasks.findByName("minify${capitalizedVariantName}WithProguard") if (proguardTask != null && proguardTask.enabled) { return proguardTask } // in case that Google changes the task name in later versions throw new GradleException(String.format("The minifyEnabled is enabled for '%s', but " + "tinker cannot find the task. Please submit issue to us: %s", variant.name, TinkerPatchPlugin.ISSUE_URL)) } static def getInstantRunTask(project, variant) { return project.tasks.findByName("transformClassesWithInstantRunFor${variant.name.capitalize()}") } static def getCollectMultiDexComponentsTask(project, variant) { return project.tasks.findByName("collect${variant.name.capitalize()}MultiDexComponents") } static def getAAPTPath(project) { def buildTools = project.android.sdkDirectory.toPath() .resolve("build-tools") .resolve(project.android.buildToolsVersion) def aaptPath = buildTools.resolve("aapt") return aaptPath } static def getAAPT2Path(project) { def buildTools = project.android.sdkDirectory.toPath() .resolve("build-tools") .resolve(project.android.buildToolsVersion) def aapt2Path = buildTools.resolve("aapt2") return aapt2Path } static def getFieldRecursively(ownerClazz, name) { def currClazz = ownerClazz as Class while (true) { try { def field = currClazz.getDeclaredField(name) if (!field.isAccessible()) { field.setAccessible(true) } return field } catch (NoSuchFieldException e) { if (currClazz != Object.class) { currClazz = currClazz.getSuperclass() } else { throw new NoSuchFieldException("Cannot find field ${name} in ${ownerClazz.getName()} and its super classes.") } } } // Should not be here. } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/TinkerPatchPlugin.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle import com.android.build.gradle.api.ApkVariant import com.tencent.tinker.build.gradle.extension.* import com.tencent.tinker.build.gradle.task.* import com.tencent.tinker.build.util.FileOperation import com.tencent.tinker.build.util.TypedValue import com.tencent.tinker.build.util.Utils import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.FileCollection import sun.misc.Unsafe import java.lang.reflect.Field /** * Registers the plugin's tasks. * * @author zhangshaowen */ class TinkerPatchPlugin implements Plugin { public static final String ISSUE_URL = "https://github.com/Tencent/tinker/issues" private Project mProject = null @Override public void apply(Project project) { mProject = project //osdetector change its plugin name in 1.4.0 try { mProject.apply plugin: 'osdetector' } catch (Throwable e) { mProject.apply plugin: 'com.google.osdetector' } mProject.extensions.create('tinkerPatch', TinkerPatchExtension) mProject.tinkerPatch.extensions.create('buildConfig', TinkerBuildConfigExtension, mProject) mProject.tinkerPatch.extensions.create('dex', TinkerDexExtension, mProject) mProject.tinkerPatch.extensions.create('lib', TinkerLibExtension) mProject.tinkerPatch.extensions.create('res', TinkerResourceExtension) mProject.tinkerPatch.extensions.create("arkHot", TinkerArkHotExtension) mProject.tinkerPatch.extensions.create('packageConfig', TinkerPackageConfigExtension, mProject) mProject.tinkerPatch.extensions.create('sevenZip', TinkerSevenZipExtension, mProject) if (!mProject.plugins.hasPlugin('com.android.application')) { throw new GradleException('generateTinkerApk: Android Application plugin required') } def android = mProject.extensions.android try { //close preDexLibraries android.dexOptions.preDexLibraries = false //open jumboMode android.dexOptions.jumboMode = true //disable dex archive mode disableArchiveDex() //禁止打了运行时注解的类全部打到主dex中 android.dexOptions.keepRuntimeAnnotatedClasses = false } catch (Throwable e) { //no preDexLibraries field, just continue } mProject.afterEvaluate { def configuration = mProject.tinkerPatch if (!configuration.tinkerEnable) { mProject.logger.error("tinker tasks are disabled.") return } mProject.logger.error("----------------------tinker build warning ------------------------------------") mProject.logger.error("tinker auto operation: ") mProject.logger.error("excluding annotation processor and source template from app packaging. Enable dx jumboMode to reduce package size.") mProject.logger.error("enable dx jumboMode to reduce package size.") mProject.logger.error("disable preDexLibraries to prevent ClassDefNotFoundException when your app is booting.") mProject.logger.error("disable archive dex mode so far for keeping dex apply.") mProject.logger.error("") mProject.logger.error("tinker will change your build configs:") mProject.logger.error("we will add TINKER_ID=${configuration.buildConfig.tinkerId} in your build output manifest file ${project.buildDir}/intermediates/manifests/full/*") mProject.logger.error("") mProject.logger.error("if minifyEnabled is true") String tempMappingPath = configuration.buildConfig.applyMapping if (FileOperation.isLegalFile(tempMappingPath)) { mProject.logger.error("we will build ${mProject.getName()} apk with apply mapping file ${tempMappingPath}") } mProject.logger.error("you will find the gen proguard rule file at ${TinkerBuildPath.getProguardConfigPath(project)}") mProject.logger.error("and we will help you to put it in the proguardFiles.") mProject.logger.error("") mProject.logger.error("if multiDexEnabled is true") mProject.logger.error("you will find the gen multiDexKeepProguard file at ${TinkerBuildPath.getMultidexConfigPath(project)}") mProject.logger.error("and we will help you to put it in the MultiDexKeepProguardFile.") mProject.logger.error("") mProject.logger.error("if applyResourceMapping file is exist") String tempResourceMappingPath = configuration.buildConfig.applyResourceMapping if (FileOperation.isLegalFile(tempResourceMappingPath)) { mProject.logger.error("we will build ${mProject.getName()} apk with resource R.txt ${tempResourceMappingPath} file") } else { mProject.logger.error("we will build ${mProject.getName()} apk with resource R.txt file") } mProject.logger.error("if resources.arsc has changed, you should use applyResource mode to build the new apk!") mProject.logger.error("-----------------------------------------------------------------") android.applicationVariants.all { ApkVariant variant -> def variantName = variant.name def capitalizedVariantName = variantName.capitalize() def instantRunTask = Compatibilities.getInstantRunTask(project, variant) if (instantRunTask != null) { throw new GradleException( "Tinker does not support instant run mode, please trigger build" + " by assemble${capitalizedVariantName} or disable instant run" + " in 'File->Settings...'." ) } TinkerPatchSchemaTask tinkerPatchBuildTask = mProject.tasks.create("tinkerPatch${capitalizedVariantName}", TinkerPatchSchemaTask) tinkerPatchBuildTask.signConfig = variant.signingConfig // Create a task to add a build TINKER_ID to AndroidManifest.xml // This task must be called after "process${variantName}Manifest", since it // requires that an AndroidManifest.xml exists in `build/intermediates`. def agpProcessManifestTask = Compatibilities.getProcessManifestTask(project, variant) def tinkerManifestAction = new TinkerManifestAction(project) agpProcessManifestTask.doLast tinkerManifestAction variant.outputs.each { variantOutput -> setPatchNewApkPath(configuration, variantOutput, variant, tinkerPatchBuildTask) setPatchOutputFolder(configuration, variantOutput, variant, tinkerPatchBuildTask) def outputName = variantOutput.dirName if (outputName.endsWith("/")) { outputName = outputName.substring(0, outputName.length() - 1) } if (tinkerManifestAction.outputNameToManifestMap.containsKey(outputName)) { throw new GradleException("Duplicate tinker manifest output name: '${outputName}'") } def manifestPath = Compatibilities.getOutputManifestPath(project, agpProcessManifestTask, variantOutput) tinkerManifestAction.outputNameToManifestMap.put(outputName, manifestPath) } def agpProcessResourcesTask = Compatibilities.getProcessResourcesTask(project, variant) //resource id TinkerResourceIdTask applyResourceTask = mProject.tasks.create("tinkerProcess${capitalizedVariantName}ResourceId", TinkerResourceIdTask) applyResourceTask.variant = variant applyResourceTask.applicationId = Compatibilities.getApplicationId(project, variant) applyResourceTask.resDir = Compatibilities.getInputResourcesDirectory(project, agpProcessResourcesTask) applyResourceTask.mustRunAfter agpProcessManifestTask agpProcessResourcesTask.dependsOn applyResourceTask // Fix issue-866. // We found some case that applyResourceTask run after mergeResourcesTask, it caused 'applyResourceMapping' config not work. // The task need merged resources to calculate ids.xml, it must depends on merge resources task. def agpMergeResourcesTask = Compatibilities.getMergeResourcesTask(project, variant) applyResourceTask.dependsOn agpMergeResourcesTask if (tinkerManifestAction.outputNameToManifestMap == null || tinkerManifestAction.outputNameToManifestMap.isEmpty()) { throw new GradleException('No manifest output path was found.') } if (applyResourceTask.resDir == null) { throw new GradleException("applyResourceTask.resDir is null.") } // Add this proguard settings file to the list boolean proguardEnable = variant.getBuildType().buildType.minifyEnabled if (proguardEnable) { def obfuscateTask = Compatibilities.getObfuscateTask(project, variant) obfuscateTask.doFirst new TinkerProguardConfigAction(variant) } // Add this multidex proguard settings file to the list boolean multiDexEnabled = variant.mergedFlavor.multiDexEnabled if (multiDexEnabled) { TinkerMultidexConfigTask multidexConfigTask = mProject.tasks.create("tinkerProcess${capitalizedVariantName}MultidexKeep", TinkerMultidexConfigTask) multidexConfigTask.applicationVariant = variant multidexConfigTask.multiDexKeepProguard = getManifestMultiDexKeepProguard(variant) // for java.io.FileNotFoundException: app/build/intermediates/multi-dex/release/manifest_keep.txt // for gradle 3.x gen manifest_keep move to processResources task multidexConfigTask.mustRunAfter agpProcessResourcesTask def agpMultidexTask = Compatibilities.getMultiDexTask(project, variant) def agpR8Task = Compatibilities.getR8Task(project, variant) if (agpMultidexTask != null) { agpMultidexTask.dependsOn multidexConfigTask } else if (agpMultidexTask == null && agpR8Task != null) { agpR8Task.dependsOn multidexConfigTask try { Object r8Transform = agpR8Task.getTransform() //R8 maybe forget to add multidex keep proguard file in agp 3.4.0, it's a agp bug! //If we don't do it, some classes will not keep in maindex such as loader's classes. //So tinker will not remove loader's classes, it will crashed in dalvik and will check TinkerTestDexLoad.isPatch failed in art. if (r8Transform.metaClass.hasProperty(r8Transform, "mainDexRulesFiles")) { File manifestMultiDexKeepProguard = getManifestMultiDexKeepProguard(variant) if (manifestMultiDexKeepProguard != null) { //see difference between mainDexRulesFiles and mainDexListFiles in https://developer.android.com/studio/build/multidex?hl=zh-cn FileCollection originalFiles = r8Transform.metaClass.getProperty(r8Transform, 'mainDexRulesFiles') if (!originalFiles.contains(manifestMultiDexKeepProguard)) { FileCollection replacedFiles = mProject.files(originalFiles, manifestMultiDexKeepProguard) mProject.logger.error("R8Transform original mainDexRulesFiles: ${originalFiles.files}") mProject.logger.error("R8Transform replaced mainDexRulesFiles: ${replacedFiles.files}") //it's final, use reflect to replace it. replaceKotlinFinalField("com.android.build.gradle.internal.transforms.R8Transform", "mainDexRulesFiles", r8Transform, replacedFiles) } } } } catch (Exception ignore) { //Maybe it's not a transform task after agp 3.6.0 so try catch it. } } def collectMultiDexComponentsTask = Compatibilities.getCollectMultiDexComponentsTask(project, variant) if (collectMultiDexComponentsTask != null) { multidexConfigTask.mustRunAfter collectMultiDexComponentsTask } } if (configuration.buildConfig.keepDexApply && FileOperation.isLegalFile(mProject.tinkerPatch.oldApk)) { com.tencent.tinker.build.gradle.transform.ImmutableDexTransform.inject(mProject, variant) } } } } /** * Specify the output folder of tinker patch result. * * @param configuration the tinker configuration 'tinkerPatch' * @param output the output of assemble result * @param variant the variant * @param tinkerPatchBuildTask the task that tinker patch uses */ void setPatchOutputFolder(configuration, output, variant, tinkerPatchBuildTask) { File parentFile = output.outputFile String outputFolder = "${configuration.outputFolder}"; if (!Utils.isNullOrNil(outputFolder)) { outputFolder = "${outputFolder}/${TypedValue.PATH_DEFAULT_OUTPUT}/${variant.dirName}" } else { outputFolder = "${parentFile.getParentFile().getParentFile().getAbsolutePath()}/${TypedValue.PATH_DEFAULT_OUTPUT}/${variant.dirName}" } tinkerPatchBuildTask.outputFolder = outputFolder } void disableArchiveDex() { try { def booleanOptClazz = Class.forName('com.android.build.gradle.options.BooleanOption') def enableDexArchiveField = booleanOptClazz.getDeclaredField('ENABLE_DEX_ARCHIVE') enableDexArchiveField.setAccessible(true) def enableDexArchiveEnumObj = enableDexArchiveField.get(null) def defValField = enableDexArchiveEnumObj.getClass().getDeclaredField('defaultValue') defValField.setAccessible(true) defValField.set(enableDexArchiveEnumObj, false) } catch (Throwable thr) { // To some extends, class not found means we are in lower version of android gradle // plugin, so just ignore that exception. if (!(thr instanceof ClassNotFoundException)) { mProject.logger.error("reflectDexArchiveFlag error: ${thr.getMessage()}.") } } } /** * Specify the new apk path. If the new apk file is specified by {@code tinkerPatch.buildConfig.newApk}, * just use it as the new apk input for tinker patch, otherwise use the assemble output. * * @param project the project which applies this plugin * @param configuration the tinker configuration 'tinkerPatch' * @param output the output of assemble result * @param variant the variant * @param tinkerPatchBuildTask the task that tinker patch uses */ void setPatchNewApkPath(configuration, output, variant, tinkerPatchBuildTask) { def newApkPath = configuration.newApk if (!Utils.isNullOrNil(newApkPath)) { if (FileOperation.isLegalFileOrDirectory(newApkPath)) { tinkerPatchBuildTask.buildApkPath = newApkPath return } } tinkerPatchBuildTask.buildApkPath = output.outputFile tinkerPatchBuildTask.dependsOn Compatibilities.getAssembleTask(mProject, variant) } void replaceKotlinFinalField(String className, String filedName, Object instance, Object fieldValue) { Field field = Class.forName(className).getDeclaredField(filedName) final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe") unsafeField.setAccessible(true) final Unsafe unsafe = (Unsafe) unsafeField.get(null) final long fieldOffset = unsafe.objectFieldOffset(field) unsafe.putObject(instance, fieldOffset, fieldValue) } File getManifestMultiDexKeepProguard(def applicationVariant) { File multiDexKeepProguard = null try { def file = applicationVariant.variantData.artifacts.get( Class.forName('com.android.build.gradle.internal.scope.InternalArtifactType$LEGACY_MULTIDEX_AAPT_DERIVED_PROGUARD_RULES') .getDeclaredField("INSTANCE") .get(null) ).getOrNull()?.getAsFile() if (file != null && file.getName() != '__EMPTY_DIR__') { multiDexKeepProguard = file } } catch (Throwable ignore) { // Ignored. } if (multiDexKeepProguard == null) { try { //for kotlin def file = applicationVariant.getVariantData().getScope().getArtifacts().getFinalProduct( Class.forName('com.android.build.gradle.internal.scope.InternalArtifactType$LEGACY_MULTIDEX_AAPT_DERIVED_PROGUARD_RULES') .getDeclaredField("INSTANCE") .get(null) ).getOrNull()?.getAsFile() if (file != null && file.getName() != '__EMPTY_DIR__') { multiDexKeepProguard = file } } catch (Throwable ignore) { // Ignored. } } if (multiDexKeepProguard == null) { try { File file = applicationVariant.getVariantData().getScope().getArtifacts().getFinalProduct( Class.forName("com.android.build.gradle.internal.scope.InternalArtifactType") .getDeclaredField("LEGACY_MULTIDEX_AAPT_DERIVED_PROGUARD_RULES") .get(null) ).getOrNull()?.getAsFile() if (file != null && file.getName() != '__EMPTY_DIR__') { multiDexKeepProguard = file } } catch (Throwable ignore) { // Ignored. } } if (multiDexKeepProguard == null) { try { def buildableArtifact = applicationVariant.getVariantData().getScope().getArtifacts().getFinalArtifactFiles( Class.forName("com.android.build.gradle.internal.scope.InternalArtifactType") .getDeclaredField("LEGACY_MULTIDEX_AAPT_DERIVED_PROGUARD_RULES") .get(null) ) //noinspection GroovyUncheckedAssignmentOfMemberOfRawType,UnnecessaryQualifiedReference multiDexKeepProguard = com.google.common.collect.Iterators.getOnlyElement(buildableArtifact.iterator()) } catch (Throwable ignore) { } } if (multiDexKeepProguard == null) { try { multiDexKeepProguard = applicationVariant.getVariantData().getScope().getManifestKeepListProguardFile() } catch (Throwable ignore) { } } if (multiDexKeepProguard == null) { try { multiDexKeepProguard = applicationVariant.getVariantData().getScope().getManifestKeepListFile() } catch (Throwable ignore) { } } if (multiDexKeepProguard == null) { mProject.logger.error("can't get multiDexKeepProguard file") } return multiDexKeepProguard } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/common/TinkerBuildPath.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle import org.gradle.api.Project /** * define the tinker build path. * * @author yanbo */ class TinkerBuildPath { private static final String TINKER_INTERMEDIATES = "/intermediates/tinker_intermediates/" private static final String MULTIDEX_CONFIG_FILE = "tinker_multidexkeep.pro" private static final String PROGUARD_CONFIG_FILE = "tinker_proguard.pro" private static final String RESOURCE_PUBLIC_XML = "public.xml" private static final String RESOURCE_IDX_XML = "idx.xml" private static final String RESOURCE_VALUES_BACKUP = "values_backup" private static final String RESOURCE_PUBLIC_TXT = "public.txt" //it's parent dir must start with values private static final String RESOURCE_TO_COMPILE_PUBLIC_XML = "aapt2/res/values/tinker_public.xml" static String getTinkerIntermediates(Project project) { return "${project.buildDir}$TINKER_INTERMEDIATES" } static String getMultidexConfigPath(Project project) { return "${getTinkerIntermediates(project)}$MULTIDEX_CONFIG_FILE" } static String getProguardConfigPath(Project project) { return "${getTinkerIntermediates(project)}$PROGUARD_CONFIG_FILE" } static String getResourcePublicXml(Project project) { return "${getTinkerIntermediates(project)}$RESOURCE_PUBLIC_XML" } static String getResourceIdxXml(Project project) { return "${getTinkerIntermediates(project)}$RESOURCE_IDX_XML" } static String getResourceValuesBackup(Project project) { return "${getTinkerIntermediates(project)}$RESOURCE_VALUES_BACKUP" } static String getResourcePublicTxt(Project project) { return "${getTinkerIntermediates(project)}$RESOURCE_PUBLIC_TXT" } static String getResourceToCompilePublicXml(Project project) { return "${getTinkerIntermediates(project)}$RESOURCE_TO_COMPILE_PUBLIC_XML" } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerArkHotExtension.groovy ================================================ /* * Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.build.gradle.extension public class TinkerArkHotExtension { String path; String name; public TinkerArkHotExtension() { path = "arkHot"; name = "patch.apk"; } @Override public String toString() { """| path= ${path} | name= ${name} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerBuildConfigExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import org.gradle.api.GradleException import org.gradle.api.Project /** * The configuration properties. * * @author zhangshaowen */ public class TinkerBuildConfigExtension { /** * Specifies the old apk's mapping file for proguard to applymapping */ String applyMapping /** * Specifies the old resource id mapping(R.txt) file to applyResourceMapping */ String applyResourceMapping /** * because we don't want to check the base apk with md5 in the runtime(it is slow) * tinkerId is use to identify the unique base apk when the patch is tried to apply. * we can use git rev, svn rev or simply versionCode. * we will gen the tinkerId in your manifest automatic */ String tinkerId /** * If true, Tinker will append name of each variant output to their tinker ids. */ boolean appendOutputNameToTinkerId /** * Whether tinker should treat the base apk as the one being protected by app * protection tools. * If this attribute is true, the generated patch package will contain a * dex including all changed classes instead of any dexdiff patch-info files. * default: false */ boolean isProtectedApp /** * Whether tinker should support component hotplug (add new component dynamically). * If this attribute is true, the component added in new apk will be available after * patch is successfully loaded. Otherwise an error would be announced when generating patch * on compile-time. * * Notice that currently this feature is incubating and only support NON-EXPORTED Activity */ boolean supportHotplugComponent private Project project boolean usingResourceMapping /** * if keepDexApply is true,class in which dex refer to the old apk. * open this can reduce the dex diff file size. */ boolean keepDexApply public TinkerBuildConfigExtension(Project project) { this.project = project applyMapping = "" applyResourceMapping = "" tinkerId = null appendOutputNameToTinkerId = false usingResourceMapping = false keepDexApply = false isProtectedApp = false } void checkParameter() { if (tinkerId == null || tinkerId.isEmpty()) { throw new GradleException("you must set your tinkerId to identify the base apk!") } } @Override public String toString() { """| applyMapping = ${applyMapping} | applyResourceMapping = ${applyResourceMapping} | isProtectedApp = ${isProtectedApp} | supportHotplugComponent = ${supportHotplugComponent} | keepDexApply = ${keepDexApply} | tinkerId = ${tinkerId} | appendOutputNameToTinkerId = ${appendOutputNameToTinkerId} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerDexExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import org.gradle.api.GradleException import org.gradle.api.Project /** * The configuration properties. * * @author zhangshaowen */ public class TinkerDexExtension { /** * raw or jar, if you want to support below 4.0, you should use jar * default: raw, keep the orginal file type */ String dexMode; /** * the dex file patterns, which dex or jar files will be deal to gen patch * such as [classes.dex, classes-*.dex, assets/multiDex/*.jar] */ Iterable pattern; /** * the loader files, they will be removed during gen patch main dex * and they should be at the primary dex * such as [com.tencent.tinker.loader.*, com.tinker.sample.MyApplication] */ Iterable loader; Iterable ignoreWarningLoader; private Project project; public TinkerDexExtension(Project project) { dexMode = "jar" pattern = [] loader = [] ignoreWarningLoader = [] this.project = project } void checkDexMode() { if (!dexMode.equals("raw") && !dexMode.equals("jar")) { throw new GradleException("dexMode can be only one of 'jar' or 'raw'!") } } @Override public String toString() { """| dexMode = ${dexMode} | pattern = ${pattern} | loader = ${loader} | ignoreWarningLoader = ${ignoreWarningLoader} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerLibExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension /** * The configuration properties. * * @author zhangshaowen */ public class TinkerLibExtension { /** * the library file patterns, which files will be deal to gen patch * such as [lib/armeabi/*.so, assets/libs/*.so] */ Iterable pattern; public TinkerLibExtension() { pattern = [] } @Override public String toString() { """| pattern = ${pattern} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPackageConfigExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import com.tencent.tinker.build.apkparser.AndroidParser import org.gradle.api.GradleException import org.gradle.api.Project /** * The configuration properties. * * @author zhangshaowen */ public class TinkerPackageConfigExtension { private static final String GLOBAL_PACKAGE_CONFIG = '__$GLOBAL_PACKAGE_CONFIG$__' /** * we can gen package config file while configField method */ private Map> fields private Project project; private AndroidParser androidManifest; public TinkerPackageConfigExtension(project) { fields = [:] this.project = project } void configField(String name, String value) { configApkSpecField(GLOBAL_PACKAGE_CONFIG, name, value) } void configApkSpecField(String apkName, String name, String value) { def pkgFieldMap = fields.get(apkName) if (pkgFieldMap == null) { pkgFieldMap = [:] fields.put(apkName, pkgFieldMap) } pkgFieldMap.put(name, value) } Map getFields() { return getApkSpecFields(GLOBAL_PACKAGE_CONFIG) } Map getApkSpecFields(String apkName) { def result = fields.get(apkName) return result != null ? result : Collections.emptyMap() } private void createApkMetaFile() { if (androidManifest == null) { File oldPakFile = new File(project.tinkerPatch.oldApk) if (!oldPakFile.exists()) { throw new GradleException( String.format("old apk file %s is not exist, you can set the value directly!", oldPakFile) ) } androidManifest = AndroidParser.getAndroidManifest(oldPakFile); } } String getVersionCodeFromOldAPk() { createApkMetaFile() return androidManifest.apkMeta.versionCode; } String getVersionCodeFromApk(File apkPath) { return AndroidParser.getAndroidManifest(apkPath).apkMeta.versionCode } String getVersionNameFromOldAPk() { createApkMetaFile() return androidManifest.apkMeta.versionName; } String getVersionNameFromApk(File apkPath) { return AndroidParser.getAndroidManifest(apkPath).apkMeta.versionName } String getMinSdkVersionFromOldAPk() { createApkMetaFile() return androidManifest.apkMeta.minSdkVersion; } String getMinSdkVersionFromApk(File apkPath) { return AndroidParser.getAndroidManifest(apkPath).apkMeta.minSdkVersion } String getMetaDataFromOldApk(String name) { createApkMetaFile() String value = androidManifest.metaDatas.get(name); if (value == null) { throw new GradleException("can't find meta data ${name} from the old apk manifest file!") } return value } String getMetaDataFromApk(File apkPath, String name) { String value = AndroidParser.getAndroidManifest(apkPath).metaDatas.get(name) if (value == null) { throw new GradleException("can't find meta data ${name} from the manifest file in [${apkPath.getAbsolutePath()}]!") } return value } @Override public String toString() { """| fields = ${fields} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerPatchExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import org.gradle.api.GradleException; /** * The configuration properties. * * @author zhangshaowen */ public class TinkerPatchExtension { /** * Specifies the old apk path to diff with the new apk */ String oldApk /** * Specify a folder for the outputs where place the tinker patch results. */ String outputFolder /** * Specify the new apk path instead of running assemble task again. */ String newApk; /** * If there is loader class changes, * or Activity, Service, Receiver, Provider change, it will terminal * if ignoreWarning is false * default: false */ boolean ignoreWarning /** * * Allow loader class existence in any class loader. * * This will suppress the exception like: *

     * loader classes are found in old secondary dex. Found classes: ...
     * loader classes are found in new secondary dex. Found classes: ...
     * 
* *

Since Android Gradle Plugin 3.3.0, there is no simply way to keep all loader classes in * the primary dex file if your application's min sdk version is 21 or above. In this situation, you * can turn the {@link #removeLoaderForAllDex} and {@link #allowLoaderInAnyDex} to true and * tolerate the loader classes to exists in any dex file. * *

default: false */ boolean allowLoaderInAnyDex /** * Whether to remove loader class for every dex file. When false, we will assume the loader * class only exists in the main dex(classes.dex). * *

If the loader class may exists in any dex, you must set this to true. Otherwise, you patch will * cause tinker runtime load failed. But this will cause a little increment on the size * of the patch file. * *

Since Android Gradle Plugin 3.3.0, there is no simply way to keep all loader classes in * the primary dex file if your application's min sdk version is 21 or above. In this situation, you * can turn the {@link #removeLoaderForAllDex} and {@link #allowLoaderInAnyDex} to true and * tolerate the loader classes to exists in any dex file. * *

default: false */ boolean removeLoaderForAllDex /** * If sign the patch file with the android signConfig * default: true */ boolean useSign /** * whether use tinker * default: true */ boolean tinkerEnable /** * customDiffPath * default: null */ String customPath /** * customDiffPathArgs * default: null */ String customDiffPathArgs public TinkerPatchExtension() { oldApk = "" outputFolder = "" newApk = "" ignoreWarning = false allowLoaderInAnyDex = false removeLoaderForAllDex = false useSign = true tinkerEnable = true customPath = null customDiffPathArgs = null } void checkParameter() { if (oldApk == null) { throw new GradleException("old apk is null, you must set the correct old apk value!") } File apk = new File(oldApk) if (!apk.exists()) { throw new GradleException("old apk ${oldApk} is not exist, you must set the correct old apk value!") } } @Override public String toString() { """| oldApk = ${oldApk} | outputFolder = ${outputFolder} | newApk = ${newApk} | ignoreWarning = ${ignoreWarning} | removeLoaderForAllDex = ${removeLoaderForAllDex} | tinkerEnable = ${tinkerEnable} | useSign = ${useSign} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerResourceExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import org.gradle.api.GradleException /** * The configuration properties. * * @author liangwenxiang */ public class TinkerResourceExtension { /** * the resource file patterns, which files will be deal to gen patch * such as [res/*, assets/*, resources.arsc] */ Iterable pattern /** * the resource file ignoreChange patterns, ignore add, delete or modify resource change * Warning, we can only use for files no relative with resources.arsc */ Iterable ignoreChange /** * the resource file ignoreChangeWarning patterns, ignore any warning caused by add, delete or * modify resource change. */ Iterable ignoreChangeWarning /** * default 100kb * for modify resource, if it is larger than 'largeModSize' * we would like to use bsdiff algorithm to reduce patch file size */ int largeModSize public TinkerResourceExtension() { pattern = [] ignoreChange = [] ignoreChangeWarning = [] largeModSize = 100 } void checkParameter() { if (largeModSize <= 0) { throw new GradleException("largeModSize must be larger than 0") } } @Override public String toString() { """| pattern = ${pattern} | exclude = ${ignoreChange} | ignoreWarning = ${ignoreChangeWarning} | largeModSize = ${largeModSize}kb """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/extension/TinkerSevenZipExtension.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.extension import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency /** * The configuration properties. * you should only set one of them, if path is Specified, it will overwrite the artifact param * @author zhangshaowen */ public class TinkerSevenZipExtension { /** * Specifies an artifact spec for downloading the executable from * repositories. spec format: '::' */ String zipArtifact /** * Specifies a local path. * if path is Specified, it will overwrite the artifact param * such as/usr/local/bin/7za * if you do not set the zipArtifact and path, We will try to use 7za directly */ String path private Project project; public TinkerSevenZipExtension(Project project) { zipArtifact = null path = null this.project = project } void resolveZipFinalPath() { if (path != null) return if (this.zipArtifact != null) { def groupId, finalArtifact, version Configuration config = project.configurations.create("sevenZipToolsLocator") { visible = false transitive = false extendsFrom = [] } (groupId, finalArtifact, version) = this.zipArtifact.split(":") def notation = [group : groupId, name : finalArtifact, version : version, classifier: project.osdetector.classifier, ext : 'exe'] // println "Resolving artifact: ${notation}" Dependency dep = project.dependencies.add(config.name, notation) File file = config.fileCollection(dep).singleFile if (!file.canExecute() && !file.setExecutable(true)) { throw new GradleException("Cannot set ${file} as executable") } // println "Resolved artifact: ${file}" this.path = file.path } //use system 7za if (this.path == null) { this.path = "7za" } } @Override public String toString() { """| zipArtifact = ${zipArtifact} | path = ${path} """.stripMargin() } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerManifestAction.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.task import com.tencent.tinker.build.gradle.TinkerBuildPath import com.tencent.tinker.build.util.FileOperation import com.tencent.tinker.commons.util.IOHelper import groovy.xml.Namespace import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task /** * The configuration properties. * * @author zhangshaowen */ public class TinkerManifestAction implements Action { static final String TINKER_ID = "TINKER_ID" static final String TINKER_ID_PREFIX = "tinker_id_" private final Project project final Map outputNameToManifestMap = new HashMap<>() TinkerManifestAction(Project project) { this.project = project } @Override void execute(Task task) { updateManifest() } private void updateManifest() { // Parse the AndroidManifest.xml String tinkerValue = project.extensions.tinkerPatch.buildConfig.tinkerId boolean appendOutputNameToTinkerId = project.extensions.tinkerPatch.buildConfig.appendOutputNameToTinkerId if (tinkerValue == null || tinkerValue.isEmpty()) { throw new GradleException('tinkerId is not set!!!') } tinkerValue = TINKER_ID_PREFIX + tinkerValue def agpIntermediatesDir = new File(project.buildDir, 'intermediates') outputNameToManifestMap.each { String outputName, File manifest -> def manifestPath = manifest.getAbsolutePath() def finalTinkerValue = tinkerValue if (appendOutputNameToTinkerId && !outputName.isEmpty()) { finalTinkerValue += "_${outputName}" } project.logger.error("tinker add ${finalTinkerValue} to your AndroidManifest.xml ${manifestPath}") writeManifestMeta(manifestPath, TINKER_ID, finalTinkerValue) addApplicationToLoaderPattern(manifestPath) File manifestFile = new File(manifestPath) if (manifestFile.exists()) { def manifestRelPath = agpIntermediatesDir.toPath().relativize(manifestFile.toPath()).toString() def manifestDestPath = new File(project.file(TinkerBuildPath.getTinkerIntermediates(project)), manifestRelPath) FileOperation.copyFileUsingStream(manifestFile, manifestDestPath) project.logger.error("tinker gen AndroidManifest.xml in ${manifestDestPath}") } } } private static void writeManifestMeta(String manifestPath, String name, String value) { def ns = new Namespace("http://schemas.android.com/apk/res/android", "android") def isr = null def pw = null try { isr = new InputStreamReader(new FileInputStream(manifestPath), "utf-8") def xml = new XmlParser().parse(isr) as Node def application = xml.application[0] as Node if (application) { def metaDataTags = application['meta-data'] // remove any old TINKER_ID elements def tinkerId = metaDataTags.findAll { it.attributes()[ns.name].equals(name) }.each { it.parent().remove(it) } // Add the new TINKER_ID element application.appendNode('meta-data', [(ns.prefix + ':name'): name, (ns.prefix + ':value'): value]) // Write the manifest file pw = new PrintWriter(manifestPath, "utf-8") def printer = new XmlNodePrinter(pw) printer.preserveWhitespace = true printer.print(xml) } } finally { IOHelper.closeQuietly(pw) IOHelper.closeQuietly(isr) } } private void addApplicationToLoaderPattern(String manifestPath) { Iterable loader = project.extensions.tinkerPatch.dex.loader String applicationName = readManifestApplicationName(manifestPath) if (applicationName != null && !loader.contains(applicationName)) { loader.add(applicationName) project.logger.error("tinker add ${applicationName} to dex loader pattern") } String loaderClass = "com.tencent.tinker.loader.*" if (!loader.contains(loaderClass)) { loader.add(loaderClass) project.logger.error("tinker add ${loaderClass} to dex loader pattern") } } private static String readManifestApplicationName(String manifestPath) { def isr = null try { isr = new InputStreamReader(new FileInputStream(manifestPath), "utf-8") def xml = new XmlParser().parse(isr) def ns = new Namespace("http://schemas.android.com/apk/res/android", "android") def application = xml.application[0] if (application) { return application.attributes()[ns.name] } else { return null } } finally { IOHelper.closeQuietly(isr) } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerMultidexConfigTask.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.task import com.tencent.tinker.build.gradle.TinkerBuildPath import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction /** * The configuration properties. * * @author zhangshaowen */ public class TinkerMultidexConfigTask extends DefaultTask { static final String MULTIDEX_CONFIG_SETTINGS = "-keep public class * implements com.tencent.tinker.entry.ApplicationLifeCycle {\n" + " (...);\n" + " void onBaseContextAttached(android.content.Context);\n" + "}\n" + "-keep public class * implements com.tencent.tinker.loader.app.ITinkerInlineFenceBridge {\n" + " (...);\n" + " void attachBaseContext(com.tencent.tinker.loader.app.TinkerApplication, android.content.Context);\n" + "}\n" + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + " (...);\n" + "}\n" + "-keep public class com.tencent.tinker.loader.NewClassLoaderInjector {\n" + " *;\n" + "}\n" + "-keep class com.tencent.tinker.loader.NewClassLoaderInjector\$DispatchClassLoader {\n" + " *;\n" + "}\n" + "-keep class com.tencent.tinker.entry.TinkerApplicationInlineFence {\n" + " *;\n" + "}\n" + "-keep class com.tencent.tinker.loader.app.TinkerInlineFenceAction {\n" + " *;\n" + "}\n" + "-keep public class * extends android.app.Application {\n" + " ();\n" + " void attachBaseContext(android.content.Context);\n" + "}\n" @Internal def applicationVariant @Input @Optional def multiDexKeepProguard public TinkerMultidexConfigTask() { group = 'tinker' } @TaskAction def updateTinkerProguardConfig() { File file = project.file(TinkerBuildPath.getMultidexConfigPath(project)) project.logger.error("try update tinker multidex keep proguard file with ${file}") // Create the directory if it doesn't exist already file.getParentFile().mkdirs() StringBuffer lines = new StringBuffer() lines.append("\n") .append("#tinker multidex keep patterns:\n") .append(MULTIDEX_CONFIG_SETTINGS) .append("\n") // This class must be placed in main dex so that we can use it to check if new pathList // in AndroidNClassLoader is fine when under the protected app (whose main dex is always encrypted). lines.append("-keep class com.tencent.tinker.loader.TinkerTestAndroidNClassLoader {\n" + " (...);\n" + "}\n") .append("\n") lines.append("#your dex.loader patterns here\n") Iterable loader = project.extensions.tinkerPatch.dex.loader for (String pattern : loader) { if (pattern.endsWith("*")) { if (!pattern.endsWith("**")) { pattern += "*" } } lines.append("-keep class " + pattern + " {\n" + " (...);\n" + "}\n") .append("\n") } // Write our recommended proguard settings to this file FileWriter fr = new FileWriter(file.path) try { for (String line : lines) { fr.write(line) } } finally { fr.close() } if (multiDexKeepProguard == null) { project.logger.error("auto add multidex keep pattern fail, you can only copy ${file} to your own multiDex keep proguard file yourself.") return } FileWriter manifestWriter = new FileWriter(multiDexKeepProguard, true) try { for (String line : lines) { manifestWriter.write(line) } } finally { manifestWriter.close() } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerPatchSchemaTask.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.task import com.tencent.tinker.build.gradle.extension.TinkerPatchExtension import com.tencent.tinker.build.patch.InputParam import com.tencent.tinker.build.patch.Runner import groovy.io.FileType import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction /** * The configuration properties. * * @author zhangshaowen */ public class TinkerPatchSchemaTask extends DefaultTask { @Internal TinkerPatchExtension configuration @Internal String buildApkPath @Internal def signConfig @Internal String outputFolder @Internal def android TinkerPatchSchemaTask() { description = 'Assemble Tinker Patch' group = 'tinker' outputs.upToDateWhen { false } configuration = project.tinkerPatch android = project.extensions.android } @TaskAction def tinkerPatch() { configuration.checkParameter() configuration.buildConfig.checkParameter() configuration.res.checkParameter() configuration.dex.checkDexMode() configuration.sevenZip.resolveZipFinalPath() InputParam.Builder builder = new InputParam.Builder() if (configuration.useSign) { if (signConfig == null) { throw new GradleException("can't the get signConfig for this build") } builder.setSignFile(signConfig.storeFile) .setKeypass(signConfig.keyPassword) .setStorealias(signConfig.keyAlias) .setStorepass(signConfig.storePassword) } def buildApkFile = new File(buildApkPath) def oldApkFile = new File(configuration.oldApk) def newApks = [] as TreeSet def oldApks = [] as TreeSet def oldApkNames = [] as HashSet def newApkNames = [] as HashSet if (buildApkFile.isDirectory() && oldApkFile.isDirectory()) { // Directory mode oldApkFile.eachFile { if (it.name.endsWith('.apk')) { oldApks << it oldApkNames << it.getName() } } buildApkFile.eachFile { if (it.name.endsWith('.apk')) { newApks << it newApkNames << it.getName() } } def unmatchedOldApkNames = new HashSet<>(oldApkNames) unmatchedOldApkNames.removeAll(newApkNames) def unmatchedNewApkNames = new HashSet<>(newApkNames) unmatchedNewApkNames.removeAll(oldApkNames) if (!unmatchedOldApkNames.isEmpty() || !unmatchedNewApkNames.isEmpty()) { throw new GradleException("Both oldApk and newApk args are directories" + " but apks inside them are not matched.\n" + " unmatched old apks: ${unmatchedOldApkNames}\n" + " unmatched new apks: ${unmatchedNewApkNames}." ) } } else if (buildApkFile.isFile() && oldApkFile.isFile()) { // File mode newApks << buildApkFile oldApks << oldApkFile } else { throw new GradleException("oldApk [${oldApkFile.getAbsolutePath()}] and newApk [${buildApkFile.getAbsolutePath()}] must be both files or directories.") } def tmpDir = new File("${project.buildDir}/tmp/tinkerPatch") tmpDir.mkdirs() def outputDir = new File(outputFolder) outputDir.mkdirs() for (def i = 0; i < newApks.size(); ++i) { def oldApk = oldApks[i] as File def newApk = newApks[i] as File def packageConfigFields = new HashMap(configuration.packageConfig.getFields()) packageConfigFields.putAll(configuration.packageConfig.getApkSpecFields(newApk.getName())) builder.setOldApk(oldApk.getAbsolutePath()) .setNewApk(newApk.getAbsolutePath()) .setOutBuilder(tmpDir.getAbsolutePath()) .setIgnoreWarning(configuration.ignoreWarning) .setAllowLoaderInAnyDex(configuration.allowLoaderInAnyDex) .setCustomDiffPath(configuration.customPath) .setCustomDiffPathArgs(configuration.customDiffPathArgs) .setRemoveLoaderForAllDex(configuration.removeLoaderForAllDex) .setDexFilePattern(new ArrayList(configuration.dex.pattern)) .setIsProtectedApp(configuration.buildConfig.isProtectedApp) .setIsComponentHotplugSupported(configuration.buildConfig.supportHotplugComponent) .setDexLoaderPattern(new ArrayList(configuration.dex.loader)) .setDexIgnoreWarningLoaderPattern(new ArrayList(configuration.dex.ignoreWarningLoader)) .setDexMode(configuration.dex.dexMode) .setSoFilePattern(new ArrayList(configuration.lib.pattern)) .setResourceFilePattern(new ArrayList(configuration.res.pattern)) .setResourceIgnoreChangePattern(new ArrayList(configuration.res.ignoreChange)) .setResourceIgnoreChangeWarningPattern(new ArrayList(configuration.res.ignoreChangeWarning)) .setResourceLargeModSize(configuration.res.largeModSize) .setUseApplyResource(configuration.buildConfig.usingResourceMapping) .setConfigFields(packageConfigFields) .setSevenZipPath(configuration.sevenZip.path) .setUseSign(configuration.useSign) .setArkHotPath(configuration.arkHot.path) .setArkHotName(configuration.arkHot.name) InputParam inputParam = builder.create() Runner.gradleRun(inputParam) def prefix = newApk.name.take(newApk.name.lastIndexOf('.')) tmpDir.eachFile(FileType.FILES) { if (!it.name.endsWith(".apk")) { return } final File dest = new File(outputDir, "${prefix}-${it.name}") it.renameTo(dest) } } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerProguardConfigAction.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.task import com.tencent.tinker.build.gradle.Compatibilities import com.tencent.tinker.build.gradle.TinkerBuildPath import com.tencent.tinker.build.util.FileOperation import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Task /** * The configuration properties. * * @author zhangshaowen */ class TinkerProguardConfigAction implements Action { static final String PROGUARD_CONFIG_SETTINGS = "-keepattributes *Annotation* \n" + "-dontwarn com.tencent.tinker.anno.AnnotationProcessor \n" + "-keep @com.tencent.tinker.anno.DefaultLifeCycle public class *\n" + "-keep public class * extends android.app.Application {\n" + " *;\n" + "}\n" + "-keep public class com.tencent.tinker.entry.ApplicationLifeCycle {\n" + " *;\n" + "}\n" + "-keep public class * implements com.tencent.tinker.entry.ApplicationLifeCycle {\n" + " *;\n" + "}\n" + "-keep public class com.tencent.tinker.loader.TinkerLoader {\n" + " *;\n" + "}\n" + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + " *;\n" + "}\n" + "-keep public class com.tencent.tinker.loader.TinkerTestDexLoad {\n" + " *;\n" + "}\n" + "-keep public class com.tencent.tinker.entry.TinkerApplicationInlineFence {\n" + " *;\n" + "}\n" def applicationVariant TinkerProguardConfigAction(variant) { applicationVariant = variant } @Override void execute(Task task) { updateTinkerProguardConfig(task.getProject()) } def updateTinkerProguardConfig(project) { def file = project.file(TinkerBuildPath.getProguardConfigPath(project)) project.logger.error("try update tinker proguard file with ${file}") // Create the directory if it doesnt exist already file.getParentFile().mkdirs() // Write our recommended proguard settings to this file FileWriter fr = new FileWriter(file.path) String applyMappingFile = project.extensions.tinkerPatch.buildConfig.applyMapping //write applymapping if (FileOperation.isLegalFile(applyMappingFile)) { project.logger.error("try add applymapping ${applyMappingFile} to build the package") fr.write("-applymapping " + applyMappingFile) fr.write("\n") } else { project.logger.error("applymapping file ${applyMappingFile} is illegal, just ignore") } fr.write(PROGUARD_CONFIG_SETTINGS) fr.write("#your dex.loader patterns here\n") //they will removed when apply Iterable loader = project.extensions.tinkerPatch.dex.loader for (String pattern : loader) { if (pattern.endsWith("*") && !pattern.endsWith("**")) { pattern += "*" } fr.write("-keep class " + pattern) fr.write("\n") } fr.close() // Add this proguard settings file to the list injectTinkerProguardRuleFile(project, file) } private void injectTinkerProguardRuleFile(project, file) { def agpObfuscateTask = Compatibilities.getObfuscateTask(project, applicationVariant) def configurationFilesOwner = null def configurationFilesField = null try { configurationFilesOwner = agpObfuscateTask configurationFilesField = Compatibilities.getFieldRecursively(configurationFilesOwner.getClass(), '__configurationFiles__') } catch (Throwable ignored) { configurationFilesOwner = null configurationFilesField = null } if (configurationFilesField == null) { try { configurationFilesOwner = agpObfuscateTask configurationFilesField = Compatibilities.getFieldRecursively(configurationFilesOwner.getClass(), 'configurationFiles') } catch (Throwable ignored) { configurationFilesOwner = null configurationFilesField = null } } if (configurationFilesField == null) { try { configurationFilesOwner = agpObfuscateTask.transform configurationFilesField = Compatibilities.getFieldRecursively(configurationFilesOwner.getClass(), 'configurationFiles') } catch (Throwable ignored) { configurationFilesOwner = null configurationFilesField = null } } def agpConfigurationFiles = null boolean isOK = false if (configurationFilesOwner != null && configurationFilesField != null) { try { agpConfigurationFiles = configurationFilesField.get(configurationFilesOwner) isOK = true } catch (Throwable ignored) { isOK = false } } if (isOK) { def mergedConfigurationFiles = project.files(agpConfigurationFiles, project.files(file)) try { configurationFilesField.set(configurationFilesOwner, mergedConfigurationFiles) def mergedConfigurationFilesForConfirm = configurationFilesField.get(configurationFilesOwner) println "Now proguard rule files are: ${mergedConfigurationFilesForConfirm.files}" } catch (Throwable ignored) { isOK = false } } if (!isOK) { throw new GradleException('Fail to inject tinker proguard rules file. Some compatibility works need to be done.') } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/task/TinkerResourceIdTask.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.task import com.android.SdkConstants import com.android.builder.internal.aapt.AaptOptions import com.google.common.collect.ImmutableList import com.tencent.tinker.build.aapt.AaptResourceCollector import com.tencent.tinker.build.aapt.AaptUtil import com.tencent.tinker.build.aapt.PatchUtil import com.tencent.tinker.build.aapt.RDotTxtEntry import com.tencent.tinker.build.gradle.Compatibilities import com.tencent.tinker.build.gradle.TinkerBuildPath import com.tencent.tinker.build.util.FileOperation import groovy.io.FileType import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import org.gradle.util.GFileUtils import sun.misc.Unsafe import java.lang.reflect.Field import java.util.regex.Matcher import java.util.regex.Pattern /** * The configuration properties. * * @author zhangshaowen */ public class TinkerResourceIdTask extends DefaultTask { @Internal def variant @Internal String resDir @Input String applicationId //if you need add public flag, set tinker.aapt2.public = true in gradle.properties @Input boolean addPublicFlagForAapt2 = false TinkerResourceIdTask() { group = 'tinker' } protected void addStableIdsFileToAdditionalParameters(def processResourcesTask) { def stableIdsFilePath = project.file(TinkerBuildPath.getResourcePublicTxt(project)).getAbsolutePath() def injectSuccess = false try { // AGP: 4.1.0 ~ addStableIdsFileForAGP410(processResourcesTask, stableIdsFilePath) injectSuccess = true } catch (Exception e) { println("tinker add additionalParameters fail with AGP 4.1.0+ method! exception=${e}") injectSuccess = false } if (!injectSuccess) { try { // AGP: 3.2.1 ~ 4.0.2 addStableIdsFileForAGP321(processResourcesTask, stableIdsFilePath) injectSuccess = true } catch (Exception e) { println("tinker add additionalParameters fail with AGP 3.2.1 ~ 4.0.2 method! exception=${e}") injectSuccess = false } } if (!injectSuccess) { throw new GradleException("rfix add additionalParameters fail! current AGP not support?") } else { println("rfix add additionalParameters done: --stable-ids=${stableIdsFilePath}") } } private static void addStableIdsFileForAGP321(Task processResourcesTask, String stableIdsFilePath) throws Exception { def taskClass = Class.forName('com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask') def aaptOptions = taskClass.metaClass.getProperty(processResourcesTask, 'aaptOptions') def parameters = aaptOptions.additionalParameters if (parameters == null || parameters instanceof AbstractList) { parameters = new ArrayList() replaceFinalField(AaptOptions.class, 'additionalParameters', aaptOptions, parameters) } if (parameters != null) { parameters.add("--stable-ids") parameters.add(stableIdsFilePath) } } private static void addStableIdsFileForAGP410(Task processResourcesTask, String stableIdsFilePath) throws Exception { def taskClass = Class.forName('com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask') def aaptAdditionalParameters = taskClass.metaClass.getProperty(processResourcesTask, 'aaptAdditionalParameters') def abstractPropertyClass = Class.forName('org.gradle.api.internal.provider.AbstractProperty') def listPropertyValue = abstractPropertyClass.metaClass.getProperty(aaptAdditionalParameters, 'value') def fixedSupplierClass = Class.forName('org.gradle.api.internal.provider.AbstractCollectionProperty$FixedSupplier') def supplierValue = fixedSupplierClass.metaClass.getProperty(listPropertyValue, 'value') def builder = new ImmutableList.Builder() builder.addAll(supplierValue.iterator()) builder.add("--stable-ids") builder.add(stableIdsFilePath) def newSupplierValue = builder.build() replaceFinalField(fixedSupplierClass, 'value', listPropertyValue, newSupplierValue) } static void replaceFinalField(Class clazz, String fieldName, Object instance, Object fieldValue) { Class currClazz = clazz Field field while (true) { try { field = currClazz.getDeclaredField(fieldName) break } catch (NoSuchFieldException e) { if (currClazz.equals(Object.class)) { throw e } else { currClazz = currClazz.getSuperclass() } } } final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe") unsafeField.setAccessible(true) final Unsafe unsafe = (Unsafe) unsafeField.get(null) final long fieldOffset = unsafe.objectFieldOffset(field) unsafe.putObject(instance, fieldOffset, fieldValue) } /** * get android gradle plugin version by reflect */ static String getAndroidGradlePluginVersionCompat() { String version = null try { Class versionModel = Class.forName("com.android.builder.model.Version") def versionFiled = versionModel.getDeclaredField("ANDROID_GRADLE_PLUGIN_VERSION") versionFiled.setAccessible(true) version = versionFiled.get(null) } catch (Exception e) { } return version } /** * get enum obj by reflect */ static T resolveEnumValue(String value, Class type) { for (T constant : type.getEnumConstants()) { if (constant.toString().equalsIgnoreCase(value)) { return constant } } return null } /** * get com.android.build.gradle.options.ProjectOptions obj by reflect */ static def getProjectOptions(Project project) { try { def basePlugin = project.getPlugins().hasPlugin('com.android.application') ? project.getPlugins().findPlugin('com.android.application') : project.getPlugins().findPlugin('com.android.library') return Class.forName("com.android.build.gradle.BasePlugin").getMetaClass().getProperty(basePlugin, 'projectOptions') } catch (Exception e) { } return null } /** * get whether aapt2 is enabled */ static boolean isAapt2EnabledCompat(Project project) { if (getAndroidGradlePluginVersionCompat() >= '3.3.0') { //when agp' version >= 3.3.0, use aapt2 default and no way to switch to aapt. return true } boolean aapt2Enabled = false try { def projectOptions = getProjectOptions(project) Object enumValue = resolveEnumValue("ENABLE_AAPT2", Class.forName("com.android.build.gradle.options.BooleanOption")) aapt2Enabled = projectOptions.get(enumValue) } catch (Exception e) { try { //retry for agp <= 2.3.3 //when agp <= 2.3.3, the field is store in com.android.build.gradle.AndroidGradleOptions Class classAndroidGradleOptions = Class.forName("com.android.build.gradle.AndroidGradleOptions") def isAapt2Enabled = classAndroidGradleOptions.getDeclaredMethod("isAapt2Enabled", Project.class) isAapt2Enabled.setAccessible(true) aapt2Enabled = isAapt2Enabled.invoke(null, project) } catch (Exception e1) { //if we can't get it, it means aapt2 is not support current. aapt2Enabled = false } } return aapt2Enabled } /** * get real name for all resources in R.txt by values files */ private Map getRealNameMap() { Map realNameMap = new HashMap<>() def mergeResourcesTask = Compatibilities.getMergeResourcesTask(project, variant) List resDirCandidateList = new ArrayList<>() try { def output = mergeResourcesTask.outputDir if (output instanceof File) { resDirCandidateList.add(output) } else { resDirCandidateList.add(output.getAsFile().get()) } } catch (Exception ignore) { } def incFolder = mergeResourcesTask.getIncrementalFolder() if (incFolder instanceof File) { resDirCandidateList.add(new File(incFolder, "merged.dir")) } else { resDirCandidateList.add(new File(incFolder.getAsFile().get(), "merged.dir")) } resDirCandidateList.each { it.eachFileRecurse(FileType.FILES) { if (it.getParentFile().getName().startsWith("values") && it.getName().startsWith("values") && it.getName().endsWith(".xml")) { File destFile = new File(project.file(TinkerBuildPath.getResourceValuesBackup(project)), "${it.getParentFile().getName()}/${it.getName()}") GFileUtils.deleteQuietly(destFile) GFileUtils.mkdirs(destFile.getParentFile()) GFileUtils.copyFile(it, destFile) } } } project.file(TinkerBuildPath.getResourceValuesBackup(project)).eachFileRecurse(FileType.FILES) { new XmlParser().parse(it).each { String originalName = "${it.@name}".toString() //replace . to _ for all types with the same converting rule if (originalName.contains('.') || originalName.contains(':')) { // only record names with '.' or ':', for sake of memory String sanitizeName = originalName.replaceAll("[.:]", "_") realNameMap.put(sanitizeName, originalName) } } } return realNameMap } /** * Extract $ prefixed resources (nested resources) from base APK using aapt dump */ private List extractDollarPrefixedResourcesFromBaseApk() { List dollarResources = new ArrayList<>() try { String oldApkPath = project.extensions.tinkerPatch?.oldApk if (oldApkPath == null || oldApkPath.isEmpty()) { project.logger.warn("oldApk is not set, cannot extract \$ prefixed resources from base APK") return dollarResources } File oldApkFile = new File(oldApkPath) if (!oldApkFile.exists()) { project.logger.warn("oldApk file does not exist: ${oldApkPath}, cannot extract \$ prefixed resources") return dollarResources } // Get aapt path final String aaptPath = Compatibilities.getAAPTPath(project) if (aaptPath == null || aaptPath.isEmpty()) { throw new GradleException('Fail to get aapt2 path', thr) } // Use aapt2 dump resources to get all resources from base APK def outputStream = new ByteArrayOutputStream() def errorStream = new ByteArrayOutputStream() project.exec { def execSpec -> execSpec.executable "${aaptPath}" execSpec.args("dump") execSpec.args("resources") execSpec.args("${oldApkFile.absolutePath}") execSpec.standardOutput = outputStream execSpec.errorOutput = errorStream } final String dumpOutput = outputStream.toString() // Output example: // spec resource 0x7f07009a com.tencent.mm2:drawable/$icon_filter_progress_indicator__0: flags=0x00000000 final String quotedAppId = Pattern.quote(applicationId) final Pattern targetPattern = Pattern.compile("\\s*spec\\s+resource\\s+(0x[0-9A-Fa-f]+?)\\s+(?:${quotedAppId}:)?(\\w+)/([^\\s\\n\\r:]+):") dumpOutput.eachLine { line -> final Matcher matcher = targetPattern.matcher(line) if (!matcher.find()) { return null } final String resourceId = matcher.group(1) final String resourceType = matcher.group(2) final String resourceName = matcher.group(3) if (resourceType.equalsIgnoreCase("styleable")) { // Skip styleable type return null } if (resourceName.startsWith('$')) { dollarResources.add("${applicationId}:${resourceType}/${resourceName} = ${resourceId}") project.logger.warn("* Found \$ prefixed resource: ${applicationId}:${resourceType}/${resourceName} = ${resourceId}") } return null } } catch (Exception e) { project.logger.error("Failed to extract \$ prefixed resources from base APK: ${e.message}", e) } return dollarResources } /** * get the sorted stable id lines */ private ArrayList getSortedStableIds(Map> rTypeResourceMap) { List sortedLines = new ArrayList<>() Map realNameMap = getRealNameMap() rTypeResourceMap?.each { key, entries -> entries.each { //the name in R.txt which has replaced . to _ //so we should get the original name for it def name = realNameMap.get(it.name) ?: it.name if (it.type == RDotTxtEntry.RType.STYLEABLE) { //ignore styleable type, also public.xml ignore it. return } else { sortedLines.add("${applicationId}:${it.type}/${name} = ${it.idValue}") } } } // Extract $ prefixed resources (nested resources) from base APK // These resources are generated by aapt2 for inline attr tags but don't appear in R.txt List dollarResources = extractDollarPrefixedResourcesFromBaseApk() sortedLines.addAll(dollarResources) //sort it and see the diff content conveniently Collections.sort(sortedLines) return sortedLines } /** * convert public.txt to public.xml */ @SuppressWarnings("GrMethodMayBeStatic") void convertPublicTxtToPublicXml(File publicTxtFile, File publicXmlFile, boolean withId) { if (publicTxtFile == null) { return } GFileUtils.deleteQuietly(publicXmlFile) GFileUtils.mkdirs(publicXmlFile.getParentFile()) GFileUtils.touch(publicXmlFile) publicXmlFile.append("") publicXmlFile.append("\n") publicXmlFile.append("") publicXmlFile.append("\n") Pattern linePattern = Pattern.compile(".*?:(.*?)/(.*?)\\s+=\\s+(.*?)") publicTxtFile?.eachLine { def line -> Matcher matcher = linePattern.matcher(line) if (matcher.matches() && matcher.groupCount() == 3) { String resType = matcher.group(1) String resName = matcher.group(2) if (resName.startsWith('$')) { project.logger.error("ignore convert to public res ${resName} because it's a nested resource") } else if (resType.equalsIgnoreCase("styleable")) { project.logger.error("ignore convert to public res ${resName} because it's a styleable resource") } else { if (withId) { publicXmlFile.append("\t\n") } else { publicXmlFile.append("\t\n") } } } } publicXmlFile.append("") } /** * compile xml file to flat file */ void compileXmlForAapt2(File xmlFile) { if (xmlFile == null || !xmlFile.exists()) { return } final String aapt2Path = Compatibilities.getAAPT2Path(project) if (aapt2Path == null || aapt2Path.isEmpty()) { throw new GradleException('Fail to get aapt2 path', thr) } project.logger.error("tinker get aapt2 path ${aapt2Path}") def mergeResourcesTask = Compatibilities.getMergeResourcesTask(project, variant) if (xmlFile.exists()) { project.exec { def execSpec -> execSpec.executable "${aapt2Path}" execSpec.args("compile") execSpec.args("--legacy") execSpec.args("-o") execSpec.args("${mergeResourcesTask.outputDir}") execSpec.args("${xmlFile}") } } } @TaskAction def applyResourceId() { String resourceMappingFile = project.extensions.tinkerPatch.buildConfig.applyResourceMapping // Parse the public.xml and ids.xml if (!FileOperation.isLegalFile(resourceMappingFile)) { project.logger.error("apply resource mapping file ${resourceMappingFile} is illegal, just ignore") return } project.logger.error("we build ${project.getName()} apk with apply resource mapping file ${resourceMappingFile}") project.extensions.tinkerPatch.buildConfig.usingResourceMapping = true Map> rTypeResourceMap = PatchUtil.readRTxt(resourceMappingFile) if (!isAapt2EnabledCompat(project)) { String idsXml = resDir + "/values/ids.xml"; String publicXml = resDir + "/values/public.xml"; FileOperation.deleteFile(idsXml); FileOperation.deleteFile(publicXml); List resourceDirectoryList = new ArrayList() resourceDirectoryList.add(resDir) AaptResourceCollector aaptResourceCollector = AaptUtil.collectResource(resourceDirectoryList, rTypeResourceMap) PatchUtil.generatePublicResourceXml(aaptResourceCollector, idsXml, publicXml) File publicFile = new File(publicXml) if (publicFile.exists()) { String resourcePublicXml = TinkerBuildPath.getResourcePublicXml(project) FileOperation.copyFileUsingStream(publicFile, project.file(resourcePublicXml)) project.logger.error("tinker gen resource public.xml in ${resourcePublicXml}") } File idxFile = new File(idsXml) if (idxFile.exists()) { String resourceIdxXml = TinkerBuildPath.getResourceIdxXml(project) FileOperation.copyFileUsingStream(idxFile, project.file(resourceIdxXml)) project.logger.error("tinker gen resource idx.xml in ${resourceIdxXml}") } } else { File stableIdsFile = project.file(TinkerBuildPath.getResourcePublicTxt(project)) FileOperation.deleteFile(stableIdsFile); ArrayList sortedLines = getSortedStableIds(rTypeResourceMap) sortedLines?.each { stableIdsFile.append("${it}\n") } def processResourcesTask = Compatibilities.getProcessResourcesTask(project, variant) processResourcesTask.doFirst { def aaptParams = project.android.aaptOptions.additionalParameters if (aaptParams != null) { if (!aaptParams.contains('--stable-ids')) { addStableIdsFileToAdditionalParameters(processResourcesTask) } else { project.logger.error('** [NOTICE] ** Manually specified stable-ids file was detected, ' + 'Tinker will give up injecting generated stable-ids file. Please ensure your stable-ids file ' + 'keep ids of all resources in base apk.') } } if (project.hasProperty("tinker.aapt2.public")) { addPublicFlagForAapt2 = project.ext["tinker.aapt2.public"]?.toString()?.toBoolean() } if (addPublicFlagForAapt2) { //if we need add public flag for resource, we need to compile public.xml to .flat file //it's parent dir must start with values File publicXmlFile = project.file(TinkerBuildPath.getResourceToCompilePublicXml(project)) //convert public.txt to public.xml convertPublicTxtToPublicXml(stableIdsFile, publicXmlFile, false) //dest file is mergeResourceTask output dir compileXmlForAapt2(publicXmlFile) } } } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/groovy/com/tencent/tinker/build/gradle/transform/ImmutableDexTransform.groovy ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License") you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.gradle.transform import com.android.annotations.NonNull import com.android.build.api.transform.* import com.android.build.gradle.internal.pipeline.TransformManager import com.android.build.gradle.internal.pipeline.TransformTask import com.google.common.base.Joiner import com.google.common.collect.Lists import com.tencent.tinker.android.dex.ClassDef import com.tencent.tinker.android.dex.Dex import com.tencent.tinker.build.gradle.TinkerBuildPath import com.tencent.tinker.build.immutable.ClassSimDef import com.tencent.tinker.build.immutable.DexRefData import com.tencent.tinker.build.util.FileOperation import com.tencent.tinker.build.util.Utils import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.execution.TaskExecutionGraph import org.gradle.api.execution.TaskExecutionGraphListener import org.gradle.api.tasks.JavaExec import java.lang.reflect.Field import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream /** * Created by wangzhi on 16/11/24. */ public class ImmutableDexTransform extends Transform { public static final String TASK_WORK_DIR = "keep_dex" private static final Joiner PATH_JOINER = Joiner.on(File.separatorChar) Project project String oldApkPath File classPreDir File baseDexDir File mainDexListFile String varName String varDirName def variant def dexTransform ImmutableDexTransform(Project project, def variant, def dexTransform) { this.dexTransform = dexTransform this.project = project this.variant = variant this.varName = variant.name.capitalize() this.varDirName = variant.getDirName() this.oldApkPath = project.tinkerPatch.oldApk if (dexTransform.mainDexListFile instanceof File) { this.mainDexListFile = dexTransform.mainDexListFile } else { this.mainDexListFile = dexTransform.mainDexListFile.getSingleFile() } } public void initFileEnv(TransformOutputProvider outputProvider) { classPreDir = getDirInWorkDir("class_pre") baseDexDir = getDirInWorkDir("base_dex") classPreDir.mkdirs() baseDexDir.mkdirs() FileOperation.cleanDir(classPreDir) FileOperation.cleanDir(baseDexDir) } private File getDirInWorkDir(String name) { return new File(PATH_JOINER.join( TinkerBuildPath.getTinkerIntermediates(project), TASK_WORK_DIR, name, varDirName) ) } @NonNull @Override public Set getOutputTypes() { return dexTransform.getOutputTypes() } @NonNull @Override public Collection getSecondaryFileInputs() { return dexTransform.getSecondaryFileInputs() } @NonNull @Override public Collection getSecondaryDirectoryOutputs() { return dexTransform.getSecondaryDirectoryOutputs() } @NonNull @Override public Map getParameterInputs() { return dexTransform.getParameterInputs() } @Override String getName() { return dexTransform.getName() } @Override Set getInputTypes() { return dexTransform.getInputTypes() } @Override Set getScopes() { return dexTransform.getScopes() } @Override boolean isIncremental() { return dexTransform.isIncremental() } @Override void transform(TransformInvocation transformInvocation) throws TransformException, IOException, InterruptedException { // because multi dex is enable,we only process jar file. List jarInputs = Lists.newArrayList() for (TransformInput input : transformInvocation.getInputs()) { jarInputs.addAll(input.getJarInputs()) } //because the multi-dex is turned on,so the jarInput.size()==1 in theory. if (jarInputs.size() != 1) { project.logger.error("jar input size is ${jarInputs.size()}, expected is 1. we will skip immutable dex!") dexTransform.transform(transformInvocation) return } //init initFileEnv(transformInvocation.getOutputProvider()) //get all old dex ArrayList oldDexList = new ArrayList<>() traversal(new ZipFile(oldApkPath), { ZipEntry zipEntry, byte[] bytes -> if (zipEntry.name.startsWith("classes") && zipEntry.name.endsWith(".dex")) { project.logger.info("find dex: ${zipEntry.name} in old apk. ") File classDxFile = new File(baseDexDir, zipEntry.name) classDxFile.withDataOutputStream { output -> output.write(bytes, 0, bytes.length) output.close() } oldDexList.add(classDxFile) } }) //hashmap:classPath <==> dexName HashMap pathDexMap = new HashMap<>() project.logger.info("old dex list is : ${oldDexList}.") //hashmap:classPath<=>dexName oldDexList.each { dexFile -> Dex dex = new Dex(dexFile) dex.classDefs().each { ClassDef classDef -> String classPath = dex.typeNames().get(classDef.typeIndex) if (pathDexMap.get(classPath)) { throw new GradleException("double class: ${classPath} in dex: ${dexFile.name} ") } pathDexMap.put(classPath, dexFile.name - ".dex") } } //the dex start index for orphan class int newDexIndex = oldDexList.size() //a hashset for maindexlist HashSet mainDexSets = initMainDexSet(mainDexListFile) project.logger.info("mainDexSets is ${mainDexSets}.") //zip file name <==> ZipOutputStream HashMap osMap = new HashMap<>() //zip file name <==> mtd count and filed count in the zip HashMap methodAndFieldsNum = new HashMap<>() //orphan class's entry <==> orphan class's bytes HashMap orphanMap = new HashMap() //all class in allClass.jar HashSet allClassSet = new HashSet<>() //process all-classes.jar processJar(jarInputs.get(0).file, allClassSet, pathDexMap, mainDexSets, methodAndFieldsNum, osMap, orphanMap) Iterator> iterator = orphanMap.entrySet().iterator() Map.Entry leaveEntry = null while (iterator.hasNext()) { boolean writeResult = true while (writeResult && iterator.hasNext()) { if (leaveEntry != null) { String newDexName = dexIndexToName(newDexIndex, "") project.logger.info("write level orphan class: ${leaveEntry.key.name} to zip: ${newDexName}") writeResult = writeClassToZip(methodAndFieldsNum, osMap, newDexName, leaveEntry.value.toByteArray(), leaveEntry.key) if (!writeResult) { throw new GradleException("add one class to a new zip failed!\n" + "\t class:" + leaveEntry.key.name + " zip: " + newDexName) } } Map.Entry entry = iterator.next() leaveEntry = entry String newDexName = dexIndexToName(newDexIndex, "") project.logger.info("write orphan class: ${entry.key.name} to zip: ${newDexName}") writeResult = writeClassToZip(methodAndFieldsNum, osMap, newDexName, entry.value.toByteArray(), entry.key) if (writeResult) { leaveEntry = null } } newDexIndex++ } osMap.each { key, value -> value.close() } //a list for all dex's path,use for checkClassConsistence mtd ArrayList dexPathList = new ArrayList<>() def dxOutDir = transformInvocation.outputProvider.getContentLocation("main", getOutputTypes(), TransformManager.SCOPE_FULL_PROJECT, Format.DIRECTORY) if (dxOutDir.exists()) { FileOperation.cleanDir(dxOutDir) } else { dxOutDir.mkdirs() } classPreDir.eachFile { classZip -> String classIndexName = classZip.name - ".jar" String dexPath = "${dxOutDir.absolutePath}/${classIndexName}.dex" dexPathList.add(dexPath) doDex(dexPath, classZip, project.android.getDexOptions()) } checkClassConsistence(dexPathList, allClassSet) } private void processJar(File jarFile, HashSet allClassSet, HashMap pathDexMap, HashSet mainDexSets, HashMap methodAndFieldsNum, HashMap osMap, HashMap orphanMap) { ZipFile zipFile = new ZipFile(jarFile) //process class in maindexlist in first traversal(zipFile, { ZipEntry zipEntry, byte[] bytes -> if (zipEntry.name.endsWith(".class")) { if (mainDexSets.contains(zipEntry.name)) { String classPath = rePathToClassPath(zipEntry.name) allClassSet.add(classPath) project.logger.info("process main dex list's class " + classPath) if (!writeClassToZip(methodAndFieldsNum, osMap, "classes", bytes, zipEntry)) { throw new GradleException("main dex is exceed the limit! reduce the class number on your main dex keep please.") } } } }) traversal(zipFile, { ZipEntry zipEntry, byte[] bytes -> if (zipEntry.name.endsWith(".class")) { String classPath = rePathToClassPath(zipEntry.name) if (!Utils.isBlank(classPath) && !allClassSet.contains(classPath)) { allClassSet.add(classPath) //get the old dex name which class be located String belongDex = belongTo(pathDexMap, classPath) //the class is new or method|fields exceeds limit if (Utils.isBlank(belongDex) || !writeClassToZip(methodAndFieldsNum, osMap, belongDex, bytes, zipEntry)) { if (Utils.isBlank(belongDex)) { project.logger.warn("find new class: " + classPath) } saveOrphan(orphanMap, zipEntry, bytes) } } else { if (Utils.isBlank(classPath)) { project.logger.error("illegal zip entry: " + zipEntry.name) } } } }) } public HashSet initMainDexSet(File mainDexList) { HashSet mainDexSets = new HashSet<>() BufferedReader reader = mainDexList.newReader() List lines = reader.readLines() lines.each { mainDexSets.add(it) } return mainDexSets } private String rePathToClassPath(String rePath) { int eIndex = rePath.lastIndexOf(".class") if (eIndex >= 0) { return "L${rePath.substring(0, eIndex)};" } else { return "" } } private void doDex(String dexPath, File classZip, def dexOptions) { def dexJar = "${project.android.getSdkDirectory()}/build-tools/${project.android.buildToolsVersion}/lib/dx.jar" def task = project.tasks.create("dx" + (classZip.name - ".jar") + varName, JavaExec.class, new Action() { @Override void execute(JavaExec javaExec) { ArrayList execArgs = new ArrayList() execArgs.add("--dex") if (dexOptions.getJumboMode()) { execArgs.add("--force-jumbo") } if (dexOptions.getIncremental()) { execArgs.add("--incremental") execArgs.add("--no-strict") } execArgs.add("--output=${dexPath}") execArgs.add(classZip.absolutePath) project.logger.info(execArgs.toString()) javaExec.setClasspath(project.files(dexJar)) javaExec.setMain("com.android.dx.command.Main") javaExec.setArgs(execArgs) } }) task.execute() } public static void inject(Project project, def variant) { project.logger.info("prepare inject dex transform ") if (!variant.mergedFlavor.multiDexEnabled) { project.logger.warn("multidex is disabled. we will not replace the dex transform.") return } if (!FileOperation.isLegalFile(project.tinkerPatch.oldApk)) { project.logger.warn("oldApk is illegal. we will not replace the dex transform.") return } try { Class.forName("com.android.build.gradle.internal.transforms.DexTransform") } catch (ClassNotFoundException e) { return } project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() { @Override public void graphPopulated(TaskExecutionGraph taskGraph) { for (Task task : taskGraph.getAllTasks()) { if (task.project != project) { continue } if (task instanceof TransformTask && task.name.toLowerCase().contains(variant.name.toLowerCase())) { if (((TransformTask) task).getTransform().getClass() == Class.forName("com.android.build.gradle.internal.transforms.DexTransform") && !(((TransformTask) task).getTransform() instanceof ImmutableDexTransform)) { project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name) def dexTransform = task.transform ImmutableDexTransform hookDexTransform = new ImmutableDexTransform(project, variant, dexTransform) project.logger.info("variant name: " + variant.name) Field field = TransformTask.class.getDeclaredField("transform") field.setAccessible(true) field.set(task, hookDexTransform) project.logger.warn("transform class after hook: " + task.transform.getClass()) break } } } } }) } void checkClassConsistence(ArrayList dexPathList, HashSet allClassSet) { project.logger.info("start check class's consistence ..") if (dexPathList == null || dexPathList.size() == 0) { throw new GradleException("immutable dex list is null! ") } project.logger.info("check dex list: " + dexPathList) HashSet dexClassSet = new HashSet<>() int classSize = 0 dexPathList.each { path -> File dexFile = new File(path) if (dexFile.isFile()) { Dex dex = new Dex(dexFile) classSize += dex.classDefs().size() for (ClassDef item : dex.classDefs()) { int index = item.typeIndex dexClassSet.add(dex.typeNames().get(index)) } } else { throw new GradleException("dex: ${dexFile} is illegal!") } } HashSet hashSet1 = new HashSet<>(dexClassSet) HashSet hashSet2 = new HashSet<>(allClassSet) hashSet1.removeAll(allClassSet) hashSet2.removeAll(dexClassSet) if (hashSet1.size() != 0 || hashSet2.size() != 0) { throw new GradleException("class is inconsistent! " + "\n\t" + "allClassSet size is " + allClassSet.size() + ",dexClassSet size is " + dexClassSet.size() + "\n" + "allClassSet has extra class: " + hashSet2 + ",\n" + "dexClassSet has extra class: " + hashSet1 + ".\n" ) } else { project.logger.info("check class consistence successful! ") } } boolean writeClassToZip(HashMap methodAndFieldsNum, HashMap osMap, String belongDex, byte[] bytes, ZipEntry zipEntry) { File jarFile = new File(classPreDir, belongDex + ".jar") DexRefData mfData = methodAndFieldsNum.get(jarFile.name) if (mfData == null) { mfData = new DexRefData() methodAndFieldsNum.put(jarFile.name, mfData) } ClassSimDef cf = new ClassSimDef(bytes, mfData.refFields, mfData.refMtds) ZipOutputStream zos = osMap.get(belongDex) if (zos == null) { project.logger.info("jarFile is ${jarFile}.") zos = new ZipOutputStream(new FileOutputStream(jarFile)) osMap.put(belongDex, zos) } if (!writeClassToZipNoCheck(mfData, cf, zos, zipEntry, bytes)) { project.logger.error("except limit! \n \tfind class ${zipEntry.name} method num: ${mfData.methodNum},field num: ${mfData.fieldNum},belong dex: ${belongDex} ") return false } else { return true } } boolean writeClassToZipNoCheck(DexRefData mfData, ClassSimDef cf, ZipOutputStream zos, ZipEntry zipEntry, byte[] bytes) { /** * In ClassSimDef, only the fields which methods referenced or in the class definition are scanned. * But in fact, some fields may be referenced in annotation. So the statistics in ClassSimDef is not complete. * The threshold is adjusted lower in order to avoid the troubles to calculate the fields referred by annotations. */ if (mfData.methodNum + cf.methodCount >= 65536 || mfData.fieldNum + cf.fieldCount >= 64536) { return false } else { mfData.methodNum += cf.methodCount mfData.fieldNum += cf.fieldCount zos.putNextEntry(zipEntry) zos.write(bytes) zos.closeEntry() return true } } void saveOrphan(HashMap orphanMap, ZipEntry zipEntry, byte[] bytes) { ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length) bos.write(bytes, 0, bytes.length) bos.flush() orphanMap.put(zipEntry, bos) } public static String getNextClassName(int index) { return "classes${index + 1}.dex" } public String dexIndexToName(int index, String suffix) { return "classes" + (index == 1 ? "" : index) + suffix } public String belongTo(HashMap pathDexMap, String classPath) { return pathDexMap.get(classPath) } public static void traversal(ZipFile zipFile, Closure callback) { try { Enumeration enumeration = zipFile.entries() while (enumeration.hasMoreElements()) { ZipEntry entry = enumeration.nextElement() callback.call(entry, zipFile.getInputStream(entry).bytes) } } catch (IOException e) { e.printStackTrace() Utils.closeQuietly(zipFile) } } } ================================================ FILE: tinker-build/tinker-patch-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.tencent.tinker.patch.properties ================================================ implementation-class=com.tencent.tinker.build.gradle.TinkerPatchPlugin ================================================ FILE: tinker-build/tinker-patch-lib/.gitignore ================================================ /build ================================================ FILE: tinker-build/tinker-patch-lib/build.gradle ================================================ apply plugin: 'java-library' version rootProject.ext.VERSION_NAME group rootProject.ext.GROUP [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' sourceCompatibility = rootProject.ext.javaVersion targetCompatibility = rootProject.ext.javaVersion dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api project(':tinker-commons') api group: 'com.tencent.mm', name: 'apk-parser-lib', version: '1.2.3' implementation group: 'com.google.guava', name: 'guava', version: '14.0.1' implementation group: 'org.ow2.asm', name: 'asm', version: '6.0' implementation (group: 'org.smali', name: 'dexlib2', version: '2.3.1') { exclude group: 'com.google.guava' } implementation group: 'dom4j', name: 'dom4j', version: '1.6.1' } sourceSets { main { java { srcDir 'src/main/java' } resources { srcDir 'src/main/resources' } } } apply from: rootProject.file('gradle/PublishArtifact.gradle') ================================================ FILE: tinker-build/tinker-patch-lib/gradle.properties ================================================ POM_ARTIFACT_ID=tinker-patch-lib POM_NAME=Tinker Patch Lib POM_PACKAGING=jar ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptResourceCollector.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.google.common.base.Joiner; import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class AaptResourceCollector { private final Map>> rTypeResourceDirectoryMap; // private final Map> rTypeIncreaseResourceDirectoryListMap; // private final Map> rTypeIncreaseResourceDirectoryMap; private final Map rTypeEnumeratorMap; private final Map originalResourceMap; private final Map> rTypeResourceMap; private final Map> rTypeIncreaseResourceMap; private final Map> duplicateResourceMap; private final Map> sanitizeTypeMap; private final Set ignoreIdSet; private int currentTypeId; public AaptResourceCollector() { this.rTypeResourceDirectoryMap = new HashMap>>(); // this.rTypeIncreaseResourceDirectoryListMap = new HashMap>(); // this.rTypeIncreaseResourceDirectoryMap = new HashMap>(); this.rTypeEnumeratorMap = new HashMap(); this.rTypeResourceMap = new HashMap>(); this.rTypeIncreaseResourceMap = new HashMap>(); this.duplicateResourceMap = new HashMap>(); this.sanitizeTypeMap = new HashMap>(); this.originalResourceMap = new HashMap(); this.ignoreIdSet = new HashSet(); // attr type must 1 this.currentTypeId = 2; } public AaptResourceCollector(Map> rTypeResourceMap) { this(); if (rTypeResourceMap != null) { Iterator>> iterator = rTypeResourceMap.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); RType rType = entry.getKey(); Set set = entry.getValue(); // this.rTypeResourceMap.put(rType, new HashSet(set)); for (RDotTxtEntry rDotTxtEntry : set) { originalResourceMap.put(rDotTxtEntry, rDotTxtEntry); ResourceIdEnumerator resourceIdEnumerator = null; if (!rDotTxtEntry.idType.equals(IdType.INT_ARRAY)) { int resourceId = Integer.decode(rDotTxtEntry.idValue.trim()).intValue(); int typeId = ((resourceId & 0x00FF0000) / 0x00010000); if (typeId >= currentTypeId) { currentTypeId = typeId + 1; } if (this.rTypeEnumeratorMap.containsKey(rType)) { resourceIdEnumerator = this.rTypeEnumeratorMap.get(rType); if (resourceIdEnumerator.currentId < resourceId) { resourceIdEnumerator.currentId = resourceId; } } else { resourceIdEnumerator = new ResourceIdEnumerator(); resourceIdEnumerator.currentId = resourceId; this.rTypeEnumeratorMap.put(rType, resourceIdEnumerator); } } } } } } public void addIntResourceIfNotPresent(RType rType, String name) { //, ResourceDirectory resourceDirectory) { if (!rTypeEnumeratorMap.containsKey(rType)) { if (rType.equals(RType.ATTR)) { rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(1)); } else { rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(currentTypeId++)); } } RDotTxtEntry entry = new FakeRDotTxtEntry(IdType.INT, rType, name); Set resourceSet = null; if (this.rTypeResourceMap.containsKey(rType)) { resourceSet = this.rTypeResourceMap.get(rType); } else { resourceSet = new HashSet(); this.rTypeResourceMap.put(rType, resourceSet); } if (!resourceSet.contains(entry)) { String idValue = String.format("0x%08x", rTypeEnumeratorMap.get(rType).next()); addResource(rType, IdType.INT, name, idValue); //, resourceDirectory); } } public void addIntArrayResourceIfNotPresent(RType rType, String name, int numValues) { // Robolectric expects the array to be populated with the right number // of values, irrespective // of what the values are. String idValue = String.format("{ %s }", Joiner.on(",").join(Collections.nCopies(numValues, "0x7f000000"))); addResource(rType, IdType.INT_ARRAY, name, idValue); } /** * add resource * * @param rType * @param idType * @param name * @param idValue */ public void addResource(RType rType, IdType idType, String name, String idValue) { Set resourceSet = null; if (this.rTypeResourceMap.containsKey(rType)) { resourceSet = this.rTypeResourceMap.get(rType); } else { resourceSet = new HashSet(); this.rTypeResourceMap.put(rType, resourceSet); } RDotTxtEntry rDotTxtEntry = new RDotTxtEntry(idType, rType, name, idValue); boolean increaseResource = false; if (!resourceSet.contains(rDotTxtEntry)) { if (this.originalResourceMap.containsKey(rDotTxtEntry)) { this.rTypeEnumeratorMap.get(rType).previous(); rDotTxtEntry = this.originalResourceMap.get(rDotTxtEntry); } else { increaseResource = true; } resourceSet.add(rDotTxtEntry); } Set increaseResourceSet = null; //new r dot txt entry if (this.rTypeIncreaseResourceMap.containsKey(rType)) { increaseResourceSet = this.rTypeIncreaseResourceMap.get(rType); } else { increaseResourceSet = new HashSet(); this.rTypeIncreaseResourceMap.put(rType, increaseResourceSet); } if (increaseResource) { increaseResourceSet.add(rDotTxtEntry); // addResourceDirectory(rType, name, resourceDirectory); } } // private void addResourceDirectory(RType rType, String name, ResourceDirectory resourceDirectory) { // if (resourceDirectory != null) { // Map resourceDirectoryMap = null; // List resourceDirectoryList = null; // if (this.rTypeIncreaseResourceDirectoryMap.containsKey(rType)) { // resourceDirectoryMap = this.rTypeIncreaseResourceDirectoryMap.get(rType); // resourceDirectoryList = this.rTypeIncreaseResourceDirectoryListMap.get(rType); // } else { // resourceDirectoryMap = new HashMap(); // this.rTypeIncreaseResourceDirectoryMap.put(rType, resourceDirectoryMap); // resourceDirectoryList = new ArrayList(); // this.rTypeIncreaseResourceDirectoryListMap.put(rType, resourceDirectoryList); // } // ResourceDirectory existResourceDirectory = null; // if (resourceDirectoryMap.containsKey(resourceDirectory)) { // existResourceDirectory = resourceDirectoryMap.get(resourceDirectory); // } else { // existResourceDirectory = resourceDirectory; // resourceDirectoryMap.put(resourceDirectory, resourceDirectory); // resourceDirectoryList.add(existResourceDirectory); // } // existResourceDirectory.resourceEntrySet.add(new ResourceEntry(name, null)); // } // } /** * is contain resource * * @param rType * @param idType * @param name * @return boolean */ public boolean isContainResource(RType rType, IdType idType, String name) { boolean result = false; if (this.rTypeResourceMap.containsKey(rType)) { Set resourceSet = this.rTypeResourceMap.get(rType); if (resourceSet.contains(new RDotTxtEntry(idType, rType, name, "0x7f000000"))) { result = true; } } return result; } /** * add r type resource name * * @param rType * @param resourceName * @param resourceDirectory */ void addRTypeResourceName(RType rType, String resourceName, String resourceValue, ResourceDirectory resourceDirectory) { Map> directoryResourceDirectoryMap = null; if (this.rTypeResourceDirectoryMap.containsKey(rType)) { directoryResourceDirectoryMap = this.rTypeResourceDirectoryMap.get(rType); } else { directoryResourceDirectoryMap = new HashMap>(); this.rTypeResourceDirectoryMap.put(rType, directoryResourceDirectoryMap); } Set resourceDirectorySet = null; if (directoryResourceDirectoryMap.containsKey(resourceDirectory.directoryName)) { resourceDirectorySet = directoryResourceDirectoryMap.get(resourceDirectory.directoryName); } else { resourceDirectorySet = new HashSet(); directoryResourceDirectoryMap.put(resourceDirectory.directoryName, resourceDirectorySet); } boolean find = false; ResourceDirectory newResourceDirectory = new ResourceDirectory(resourceDirectory.directoryName, resourceDirectory.resourceFullFilename); if (!resourceDirectorySet.contains(newResourceDirectory)) { resourceDirectorySet.add(newResourceDirectory); } for (ResourceDirectory oldResourceDirectory : resourceDirectorySet) { if (oldResourceDirectory.resourceEntrySet.contains(new ResourceEntry(resourceName, resourceValue))) { find = true; String resourceKey = rType + "/" + resourceDirectory.directoryName + "/" + resourceName; Set fullFilenameSet = null; if (!this.duplicateResourceMap.containsKey(resourceKey)) { fullFilenameSet = new HashSet(); fullFilenameSet.add(oldResourceDirectory.resourceFullFilename); this.duplicateResourceMap.put(resourceKey, fullFilenameSet); } else { fullFilenameSet = this.duplicateResourceMap.get(resourceKey); } fullFilenameSet.add(resourceDirectory.resourceFullFilename); } } if (!find) { for (ResourceDirectory oldResourceDirectory : resourceDirectorySet) { if (oldResourceDirectory.equals(newResourceDirectory)) { if (!oldResourceDirectory.resourceEntrySet.contains(new ResourceEntry(resourceName, resourceValue))) { oldResourceDirectory.resourceEntrySet.add(new ResourceEntry(resourceName, resourceValue)); } } } } } void putSanitizeName(RType rType, String sanitizeName, String rawName) { HashMap sanitizeNameMap; if (!sanitizeTypeMap.containsKey(rType)) { sanitizeNameMap = new HashMap<>(); sanitizeTypeMap.put(rType, sanitizeNameMap); } else { sanitizeNameMap = sanitizeTypeMap.get(rType); } if (!sanitizeNameMap.containsKey(sanitizeName)) { sanitizeNameMap.put(sanitizeName, rawName); } } /** * get raw name * * @param sanitizeName * @return String */ public String getRawName(RType rType, String sanitizeName) { if (!sanitizeTypeMap.containsKey(rType)) { return null; } return this.sanitizeTypeMap.get(rType).get(sanitizeName); } /** * get r type resource map * * @return Map> */ public Map> getRTypeResourceMap() { return this.rTypeResourceMap; } /** * @return the duplicateResourceMap */ public Map> getDuplicateResourceMap() { return duplicateResourceMap; } /** * @return the rTypeIncreaseResourceMap */ public Map> getRTypeIncreaseResourceMap() { return rTypeIncreaseResourceMap; } /** * @return the rTypeResourceDirectoryMap */ public Map>> getRTypeResourceDirectoryMap() { return rTypeResourceDirectoryMap; } // /** // * @return the rTypeIncreaseResourceDirectoryListMap // */ // public Map> getRTypeIncreaseResourceDirectoryListMap() { // return rTypeIncreaseResourceDirectoryListMap; // } void addIgnoreId(String name) { ignoreIdSet.add(name); } /** * @return the ignoreIdSet */ public Set getIgnoreIdSet() { return ignoreIdSet; } private static class ResourceIdEnumerator { private int currentId = 0; ResourceIdEnumerator() { } ResourceIdEnumerator(int typeId) { this.currentId = 0x7f000000 + 0x10000 * typeId + -1; } int previous() { return --currentId; } int next() { return ++currentId; } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/AaptUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; import com.tencent.tinker.commons.util.IOHelper; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; public final class AaptUtil { private static final String ID_DEFINITION_PREFIX = "@+id/"; private static final String ITEM_TAG = "item"; private static final XPathExpression ANDROID_ID_USAGE = createExpression("//@*[starts-with(., '@') and " + "not(starts-with(., '@+')) and " + "not(starts-with(., '@android:')) and " + "not(starts-with(., '@null'))]"); private static final XPathExpression ANDROID_ID_DEFINITION = createExpression("//@*[starts-with(., '@+') and " + "not(starts-with(., '@+android:id')) and " + "not(starts-with(., '@+id/android:'))]"); private static final Map RESOURCE_TYPES = getResourceTypes(); private static final List IGNORED_TAGS = Arrays.asList("eat-comment", "skip"); private static XPathExpression createExpression(String expressionStr) { try { return XPathFactory.newInstance().newXPath().compile(expressionStr); } catch (XPathExpressionException e) { throw new AaptUtilException(e); } } private static Map getResourceTypes() { Map types = new HashMap(); for (RType rType : RType.values()) { types.put(rType.toString(), rType); } types.put("string-array", RType.ARRAY); types.put("integer-array", RType.ARRAY); types.put("declare-styleable", RType.STYLEABLE); return types; } public static AaptResourceCollector collectResource(List resourceDirectoryList) { return collectResource(resourceDirectoryList, null); } public static AaptResourceCollector collectResource(List resourceDirectoryList, Map> rTypeResourceMap) { AaptResourceCollector resourceCollector = new AaptResourceCollector(rTypeResourceMap); List references = new ArrayList<>(); for (String resourceDirectory : resourceDirectoryList) { try { collectResources(resourceDirectory, resourceCollector); } catch (Exception e) { throw new RuntimeException(e); } } for (String resourceDirectory : resourceDirectoryList) { try { processXmlFilesForIds(resourceDirectory, references, resourceCollector); } catch (Exception e) { throw new RuntimeException(e); } } return resourceCollector; } public static void processXmlFilesForIds(String resourceDirectory, List references, AaptResourceCollector resourceCollector) throws Exception { List xmlFullFilenameList = FileUtil.findMatchFile(resourceDirectory, Constant.Symbol.DOT + Constant.File.XML); if (xmlFullFilenameList != null) { for (String xmlFullFilename : xmlFullFilenameList) { File xmlFile = new File(xmlFullFilename); String parentFullFilename = xmlFile.getParent(); File parentFile = new File(parentFullFilename); if (isAValuesDirectory(parentFile.getName()) || parentFile.getName().startsWith("raw")) { // Ignore files under values* directories and raw*. continue; } processXmlFile(xmlFullFilename, references, resourceCollector); } } } private static void collectResources(String resourceDirectory, AaptResourceCollector resourceCollector) throws Exception { File resourceDirectoryFile = new File(resourceDirectory); File[] fileArray = resourceDirectoryFile.listFiles(); if (fileArray != null) { for (File file : fileArray) { if (file.isDirectory()) { String directoryName = file.getName(); if (directoryName.startsWith("values")) { if (!isAValuesDirectory(directoryName)) { throw new AaptUtilException("'" + directoryName + "' is not a valid values directory."); } processValues(file.getAbsolutePath(), resourceCollector); } else { processFileNamesInDirectory(file.getAbsolutePath(), resourceCollector); } } } } } /** * is a value directory * * @param directoryName * @return boolean */ public static boolean isAValuesDirectory(String directoryName) { if (directoryName == null) { throw new NullPointerException("directoryName can not be null"); } return directoryName.equals("values") || directoryName.startsWith("values-"); } public static void processFileNamesInDirectory(String resourceDirectory, AaptResourceCollector resourceCollector) throws IOException { File resourceDirectoryFile = new File(resourceDirectory); String directoryName = resourceDirectoryFile.getName(); int dashIndex = directoryName.indexOf('-'); if (dashIndex != -1) { directoryName = directoryName.substring(0, dashIndex); } if (!RESOURCE_TYPES.containsKey(directoryName)) { throw new AaptUtilException(resourceDirectoryFile.getAbsolutePath() + " is not a valid resource sub-directory."); } File[] fileArray = resourceDirectoryFile.listFiles(); if (fileArray != null) { for (File file : fileArray) { if (file.isHidden()) { continue; } String filename = file.getName(); int dotIndex = filename.indexOf('.'); String resourceName = dotIndex != -1 ? filename.substring(0, dotIndex) : filename; RType rType = RESOURCE_TYPES.get(directoryName); resourceCollector.addIntResourceIfNotPresent(rType, resourceName); ResourceDirectory resourceDirectoryBean = new ResourceDirectory(file.getParentFile().getName(), file.getAbsolutePath()); resourceCollector.addRTypeResourceName(rType, resourceName, null, resourceDirectoryBean); } } } public static void processValues(String resourceDirectory, AaptResourceCollector resourceCollector) throws Exception { File resourceDirectoryFile = new File(resourceDirectory); File[] fileArray = resourceDirectoryFile.listFiles(); if (fileArray != null) { for (File file : fileArray) { if (file.isHidden()) { continue; } if (!file.isFile()) { // warning continue; } processValuesFile(file.getAbsolutePath(), resourceCollector); } } } public static void processValuesFile(String valuesFullFilename, AaptResourceCollector resourceCollector) throws Exception { Document document = JavaXmlUtil.parse(valuesFullFilename); String directoryName = new File(valuesFullFilename).getParentFile().getName(); Element root = document.getDocumentElement(); for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } String resourceType = node.getNodeName(); if (resourceType.equals(ITEM_TAG)) { resourceType = node.getAttributes().getNamedItem("type").getNodeValue(); if (resourceType.equals("id")) { resourceCollector.addIgnoreId(node.getAttributes().getNamedItem("name").getNodeValue()); } } if (IGNORED_TAGS.contains(resourceType)) { continue; } if (!RESOURCE_TYPES.containsKey(resourceType)) { throw new AaptUtilException("Invalid resource type '<" + resourceType + ">' in '" + valuesFullFilename + "'."); } RType rType = RESOURCE_TYPES.get(resourceType); String resourceValue = null; switch (rType) { case STRING: case COLOR: case DIMEN: case DRAWABLE: case BOOL: case INTEGER: resourceValue = node.getTextContent().trim(); break; case ARRAY://has sub item case PLURALS://has sub item case STYLE://has sub item case STYLEABLE://has sub item resourceValue = subNodeToString(node); break; case FRACTION://no sub item resourceValue = nodeToString(node, true); break; case ATTR://no sub item resourceValue = nodeToString(node, true); break; default: break; } try { addToResourceCollector(resourceCollector, new ResourceDirectory(directoryName, valuesFullFilename), node, rType, resourceValue); } catch (Exception e) { throw new AaptUtilException(e.getMessage() + ",Process file error:" + valuesFullFilename, e); } } } public static void processXmlFile(String xmlFullFilename, List references, AaptResourceCollector resourceCollector) throws IOException, XPathExpressionException { Document document = JavaXmlUtil.parse(xmlFullFilename); NodeList nodesWithIds = (NodeList) ANDROID_ID_DEFINITION.evaluate(document, XPathConstants.NODESET); for (int i = 0; i < nodesWithIds.getLength(); i++) { String resourceName = nodesWithIds.item(i).getNodeValue(); if (!resourceName.startsWith(ID_DEFINITION_PREFIX)) { throw new AaptUtilException("Invalid definition of a resource: '" + resourceName + "'"); } resourceCollector.addIntResourceIfNotPresent(RType.ID, resourceName.substring(ID_DEFINITION_PREFIX.length())); } NodeList nodesUsingIds = (NodeList) ANDROID_ID_USAGE.evaluate(document, XPathConstants.NODESET); for (int i = 0; i < nodesUsingIds.getLength(); i++) { String resourceName = nodesUsingIds.item(i).getNodeValue(); int slashPosition = resourceName.indexOf('/'); if (slashPosition < 0) { continue; } String rawRType = resourceName.substring(1, slashPosition); String name = resourceName.substring(slashPosition + 1); if (name.startsWith("android:")) { continue; } if (rawRType.startsWith("tools:")) { continue; } if (!RESOURCE_TYPES.containsKey(rawRType)) { throw new AaptUtilException("Invalid reference '" + resourceName + "' in '" + xmlFullFilename + "'"); } RType rType = RESOURCE_TYPES.get(rawRType); // if (!resourceCollector.isContainResource(rType, IdType.INT, sanitizeName(resourceCollector, name))) { // throw new AaptUtilException("Not found reference '" + resourceName + "' in '" + xmlFullFilename + "'"); // } references.add(new FakeRDotTxtEntry(IdType.INT, rType, sanitizeName(rType, resourceCollector, name))); } } private static void addToResourceCollector(AaptResourceCollector resourceCollector, ResourceDirectory resourceDirectory, Node node, RType rType, String resourceValue) { String resourceName = sanitizeName(rType, resourceCollector, extractNameAttribute(node)); resourceCollector.addRTypeResourceName(rType, resourceName, resourceValue, resourceDirectory); if (rType.equals(RType.STYLEABLE)) { int count = 0; for (Node attrNode = node.getFirstChild(); attrNode != null; attrNode = attrNode.getNextSibling()) { if (attrNode.getNodeType() != Node.ELEMENT_NODE || !attrNode.getNodeName().equals("attr")) { continue; } String rawAttrName = extractNameAttribute(attrNode); String attrName = sanitizeName(rType, resourceCollector, rawAttrName); resourceCollector.addResource(RType.STYLEABLE, IdType.INT, String.format("%s_%s", resourceName, attrName), Integer.toString(count++)); if (!rawAttrName.startsWith("android:")) { resourceCollector.addIntResourceIfNotPresent(RType.ATTR, rawAttrName); resourceCollector.addRTypeResourceName(RType.ATTR, rawAttrName, nodeToString(attrNode, true), resourceDirectory); } } resourceCollector.addIntArrayResourceIfNotPresent(rType, resourceName, count); } else { resourceCollector.addIntResourceIfNotPresent(rType, resourceName); } } private static String sanitizeName(RType rType, AaptResourceCollector resourceCollector, String rawName) { String sanitizeName = rawName.replaceAll("[.:]", "_"); resourceCollector.putSanitizeName(rType, sanitizeName, rawName); return sanitizeName; } private static String extractNameAttribute(Node node) { return node.getAttributes().getNamedItem("name").getNodeValue(); } /** * merge package r type resource map * * @param packageRTypeResourceMapList * @return Map>> */ public static Map>> mergePackageRTypeResourceMap(List packageRTypeResourceMapList) { Map>> packageRTypeResourceMergeMap = new HashMap>>(); Map aaptResourceCollectorMap = new HashMap(); for (PackageRTypeResourceMap packageRTypeResourceMap : packageRTypeResourceMapList) { String packageName = packageRTypeResourceMap.packageName; Map> rTypeResourceMap = packageRTypeResourceMap.rTypeResourceMap; AaptResourceCollector aaptResourceCollector = null; if (aaptResourceCollectorMap.containsKey(packageName)) { aaptResourceCollector = aaptResourceCollectorMap.get(packageName); } else { aaptResourceCollector = new AaptResourceCollector(); aaptResourceCollectorMap.put(packageName, aaptResourceCollector); } Iterator>> iterator = rTypeResourceMap.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); RType rType = entry.getKey(); Set rDotTxtEntrySet = entry.getValue(); for (com.tencent.tinker.build.aapt.RDotTxtEntry rDotTxtEntry : rDotTxtEntrySet) { if (rDotTxtEntry.idType.equals(IdType.INT)) { aaptResourceCollector.addIntResourceIfNotPresent(rType, rDotTxtEntry.name); } else if (rDotTxtEntry.idType.equals(IdType.INT_ARRAY)) { aaptResourceCollector.addResource(rType, rDotTxtEntry.idType, rDotTxtEntry.name, rDotTxtEntry.idValue.trim()); } } } } Iterator> iterator = aaptResourceCollectorMap.entrySet().iterator(); while (iterator.hasNext()) { Entry entry = iterator.next(); packageRTypeResourceMergeMap.put(entry.getKey(), entry.getValue().getRTypeResourceMap()); } return packageRTypeResourceMergeMap; } /** * write R.java * * @param outputDirectory * @param packageName * @param rTypeResourceMap * @param isFinal */ public static void writeRJava(String outputDirectory, String packageName, Map> rTypeResourceMap, boolean isFinal) { String outputFullFilename = new File(outputDirectory).getAbsolutePath() + Constant.Symbol.SLASH_LEFT + (packageName.replace(Constant.Symbol.DOT, Constant.Symbol.SLASH_LEFT) + Constant.Symbol.SLASH_LEFT + "R" + Constant.Symbol.DOT + Constant.File.JAVA); FileUtil.createFile(outputFullFilename); PrintWriter writer = null; try { writer = new PrintWriter(new FileOutputStream(outputFullFilename)); writer.format("package %s;\n\n", packageName); writer.println("public final class R {\n"); for (RType rType : rTypeResourceMap.keySet()) { // Now start the block for the new type. writer.format(" public static final class %s {\n", rType.toString()); for (com.tencent.tinker.build.aapt.RDotTxtEntry rDotTxtEntry : rTypeResourceMap.get(rType)) { // Write out the resource. // Write as an int. writer.format(" public static%s%s %s=%s;\n", isFinal ? " final " : " ", rDotTxtEntry.idType, rDotTxtEntry.name, rDotTxtEntry.idValue.trim()); } writer.println(" }\n"); } // Close the class definition. writer.println("}"); } catch (Exception e) { throw new AaptUtilException(e); } finally { IOHelper.closeQuietly(writer); } } /** * write R.java * * @param outputDirectory * @param packageRTypeResourceMap * @param isFinal * @throws IOException */ public static void writeRJava(String outputDirectory, Map>> packageRTypeResourceMap, boolean isFinal) { for (String packageName : packageRTypeResourceMap.keySet()) { Map> rTypeResourceMap = packageRTypeResourceMap.get(packageName); writeRJava(outputDirectory, packageName, rTypeResourceMap, isFinal); } } private static String subNodeToString(Node node) { StringBuilder stringBuilder = new StringBuilder(); if (node != null) { NodeList nodeList = node.getChildNodes(); stringBuilder.append(nodeToString(node, false)); stringBuilder.append(StringUtil.CRLF_STRING); int nodeListLength = nodeList.getLength(); for (int i = 0; i < nodeListLength; i++) { Node childNode = nodeList.item(i); if (childNode.getNodeType() != Node.ELEMENT_NODE) { continue; } stringBuilder.append(nodeToString(childNode, true)); stringBuilder.append(StringUtil.CRLF_STRING); } if (stringBuilder.length() > StringUtil.CRLF_STRING.length()) { stringBuilder.delete(stringBuilder.length() - StringUtil.CRLF_STRING.length(), stringBuilder.length()); } } return stringBuilder.toString(); } private static String nodeToString(Node node, boolean isNoChild) { StringBuilder stringBuilder = new StringBuilder(); if (node != null) { stringBuilder.append(node.getNodeName()); NamedNodeMap namedNodeMap = node.getAttributes(); stringBuilder.append(Constant.Symbol.MIDDLE_BRACKET_LEFT); int namedNodeMapLength = namedNodeMap.getLength(); for (int j = 0; j < namedNodeMapLength; j++) { Node attributeNode = namedNodeMap.item(j); stringBuilder.append(Constant.Symbol.AT + attributeNode.getNodeName() + Constant.Symbol.EQUAL + attributeNode.getNodeValue()); if (j < namedNodeMapLength - 1) { stringBuilder.append(Constant.Symbol.COMMA); } } stringBuilder.append(Constant.Symbol.MIDDLE_BRACKET_RIGHT); String value = StringUtil.nullToBlank(isNoChild ? node.getTextContent() : node.getNodeValue()).trim(); if (StringUtil.isNotBlank(value)) { stringBuilder.append(Constant.Symbol.EQUAL + value); } } return stringBuilder.toString(); } public static class PackageRTypeResourceMap { private String packageName = null; private Map> rTypeResourceMap = null; public PackageRTypeResourceMap(String packageName, Map> rTypeResourceMap) { this.packageName = packageName; this.rTypeResourceMap = rTypeResourceMap; } } public static class AaptUtilException extends RuntimeException { private static final long serialVersionUID = 1702278793911780809L; public AaptUtilException(String message) { super(message); } public AaptUtilException(Throwable cause) { super(cause); } public AaptUtilException(String message, Throwable cause) { super(message, cause); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Constant.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; public interface Constant { interface Base { String EXCEPTION = "exception"; } interface Symbol { /** * dot "." */ String DOT = "."; char DOT_CHAR = '.'; /** * comma "," */ String COMMA = ","; /** * colon ":" */ String COLON = ":"; /** * semicolon ";" */ String SEMICOLON = ";"; /** * equal "=" */ String EQUAL = "="; /** * and "&" */ String AND = "&"; /** * question mark "?" */ String QUESTION_MARK = "?"; /** * wildcard "*" */ String WILDCARD = "*"; /** * underline "_" */ String UNDERLINE = "_"; /** * at "@" */ String AT = "@"; /** * minus "-" */ String MINUS = "-"; /** * logic and "&&" */ String LOGIC_AND = "&&"; /** * logic or "||" */ String LOGIC_OR = "||"; /** * brackets begin "(" */ String BRACKET_LEFT = "("; /** * brackets end ")" */ String BRACKET_RIGHT = ")"; /** * middle bracket left "[" */ String MIDDLE_BRACKET_LEFT = "["; /** * middle bracket right "]" */ String MIDDLE_BRACKET_RIGHT = "]"; /** * big bracket "{" */ String BIG_BRACKET_LEFT = "{"; /** * big bracket "}" */ String BIG_BRACKET_RIGHT = "}"; /** * slash "/" */ String SLASH_LEFT = "/"; /** * slash "\" */ String SLASH_RIGHT = "\\"; /** * xor or regex begin "^" */ String XOR = "^"; /** * dollar or regex end "$" */ String DOLLAR = "$"; /** * single quotes "'" */ String SINGLE_QUOTES = "'"; /** * double quotes "\"" */ String DOUBLE_QUOTES = "\""; } interface Encoding { /** * encoding */ String ISO88591 = "ISO-8859-1"; String GB2312 = "GB2312"; String GBK = "GBK"; String UTF8 = "UTF-8"; } interface Timezone { String ASIA_SHANGHAI = "Asia/Shanghai"; } interface Http { interface RequestMethod { /** * for request method */ String PUT = "PUT"; String DELETE = "DELETE"; String GET = "GET"; String POST = "POST"; String HEAD = "HEAD"; String OPTIONS = "OPTIONS"; String TRACE = "TRACE"; } interface HeaderKey { /** * for request,response header */ String CONTENT_TYPE = "Content-Type"; String CONTENT_DISPOSITION = "Content-Disposition"; String ACCEPT_CHARSET = "Accept-Charset"; String CONTENT_ENCODING = "Content-Encoding"; } interface ContentType { /** * for request,response content type */ String TEXT_PLAIN = "text/plain"; String APPLICATION_X_DOWNLOAD = "application/x-download"; String APPLICATION_ANDROID_PACKAGE = "application/vnd.android.package-archive"; String MULTIPART_FORM_DATA = "multipart/form-data"; String APPLICATION_OCTET_STREAM = "application/octet-stream"; String BINARY_OCTET_STREAM = "binary/octet-stream"; String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; } interface StatusCode { int CONTINUE = 100; int SWITCHING_PROTOCOLS = 101; int PROCESSING = 102; int OK = 200; int CREATED = 201; int ACCEPTED = 202; int NON_AUTHORITATIVE_INFORMATION = 203; int NO_CONTENT = 204; int RESET_CONTENT = 205; int PARTIAL_CONTENT = 206; int MULTI_STATUS = 207; int MULTIPLE_CHOICES = 300; int MOVED_PERMANENTLY = 301; int FOUND = 302; int SEE_OTHER = 303; int NOT_MODIFIED = 304; int USE_PROXY = 305; int SWITCH_PROXY = 306; int TEMPORARY_REDIRECT = 307; int BAD_REQUEST = 400; int UNAUTHORIZED = 401; int PAYMENT_REQUIRED = 402; int FORBIDDEN = 403; int NOT_FOUND = 404; int METHOD_NOT_ALLOWED = 405; int NOT_ACCEPTABLE = 406; int REQUEST_TIMEOUT = 408; int CONFLICT = 409; int GONE = 410; int LENGTH_REQUIRED = 411; int PRECONDITION_FAILED = 412; int REQUEST_URI_TOO_LONG = 414; int EXPECTATION_FAILED = 417; int TOO_MANY_CONNECTIONS = 421; int UNPROCESSABLE_ENTITY = 422; int LOCKED = 423; int FAILED_DEPENDENCY = 424; int UNORDERED_COLLECTION = 425; int UPGRADE_REQUIRED = 426; int RETRY_WITH = 449; int INTERNAL_SERVER_ERROR = 500; int NOT_IMPLEMENTED = 501; int BAD_GATEWAY = 502; int SERVICE_UNAVAILABLE = 503; int GATEWAY_TIMEOUT = 504; int HTTP_VERSION_NOT_SUPPORTED = 505; int VARIANT_ALSO_NEGOTIATES = 506; int INSUFFICIENT_STORAGE = 507; int LOOP_DETECTED = 508; int BANDWIDTH_LIMIT_EXCEEDED = 509; int NOT_EXTENDED = 510; int UNPARSEABLE_RESPONSE_HEADERS = 600; } } interface RequestScope { String SESSION = "session"; } interface RequestParameter { String RETURN_URL = "returnUrl"; } interface Database { String COLUMN_NAME_TOTAL = "TOTAL"; interface MySql { /** * pagination */ String PAGINATION = "LIMIT"; } } interface Capacity { /** * bytes per kilobytes */ int BYTES_PER_KB = 1024; /** * bytes per millionbytes */ int BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB; } interface Method { String PREFIX_SET = "set"; String PREFIX_GET = "get"; String PREFIX_IS = "is"; String GET_CLASS = "getClass"; } interface File { String CLASS = "class"; String JPEG = "jpeg"; String JPG = "jpg"; String GIF = "gif"; String JAR = "jar"; String JAVA = "java"; String EXE = "exe"; String DEX = "dex"; String AIDL = "aidl"; String SO = "so"; String XML = "xml"; String CSV = "csv"; String TXT = "txt"; String APK = "apk"; } interface Protocol { String FILE = "file://"; String HTTP = "http://"; String FTP = "ftp://"; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FakeRDotTxtEntry.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; /** * An {@link RDotTxtEntry} with fake {@link #idValue}, useful for comparing two resource entries for * equality, since {@link RDotTxtEntry#compareTo(RDotTxtEntry)} ignores the id value. */ public class FakeRDotTxtEntry extends RDotTxtEntry { private static final String FAKE_ID = "0x00000000"; public FakeRDotTxtEntry(IdType idType, RType type, String name) { super(idType, type, name, FAKE_ID); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileCopyException.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; public class FileCopyException extends RuntimeException { /** * serialVersionUID */ private static final long serialVersionUID = -6670157031514003361L; /** * @param message */ public FileCopyException(String message) { super(message); } /** * @param cause */ public FileCopyException(Throwable cause) { super(cause); } /** * @param message * @param cause */ public FileCopyException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/FileUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; public final class FileUtil { private FileUtil() { } /** * is file exist,include directory or file * * @param path directory or file * @return boolean */ public static boolean isExist(String path) { File file = new File(path); return file.exists(); } /** * create directory * * @param directoryPath */ public static void createDirectory(final String directoryPath) { File file = new File(directoryPath); if (!file.exists()) { file.setReadable(true, false); file.setWritable(true, true); file.mkdirs(); } } /** * create file,full filename,signle empty file. * * @param fullFilename * @return boolean */ public static boolean createFile(final String fullFilename) { boolean result = false; File file = new File(fullFilename); createDirectory(file.getParent()); try { file.setReadable(true, false); file.setWritable(true, true); result = file.createNewFile(); } catch (Exception e) { throw new FileUtilException(e); } return result; } /** * find match file * * @param sourceDirectory * @param fileSuffix * @return List */ public static List findMatchFile(String sourceDirectory, String fileSuffix) { return findMatchFileOrMatchFileDirectory(sourceDirectory, fileSuffix, null, true, true); } /** * find match file or match file directory * * @param sourceDirectory * @param fileSuffix * @param somethingAppendToRear * @param isFindMatchFile * @param includeHidden * @return List */ private static List findMatchFileOrMatchFileDirectory(String sourceDirectory, String fileSuffix, String somethingAppendToRear, boolean isFindMatchFile, boolean includeHidden) { fileSuffix = StringUtil.nullToBlank(fileSuffix); somethingAppendToRear = StringUtil.nullToBlank(somethingAppendToRear); List list = new ArrayList(); File sourceDirectoryFile = new File(sourceDirectory); Queue queue = new ConcurrentLinkedQueue(); queue.add(sourceDirectoryFile); while (!queue.isEmpty()) { File file = queue.poll(); if (file.isHidden() && !includeHidden) { continue; } if (file.isDirectory()) { File[] fileArray = file.listFiles(); if (fileArray != null) { queue.addAll(Arrays.asList(fileArray)); } } else if (file.isFile()) { if (file.getName().toLowerCase().endsWith(fileSuffix.toLowerCase())) { if (isFindMatchFile) { list.add(file.getAbsolutePath() + somethingAppendToRear); } else { String parentPath = file.getParent(); parentPath = parentPath + somethingAppendToRear; if (!list.contains(parentPath)) { list.add(parentPath); } } } } } return list; } public static class FileUtilException extends RuntimeException { private static final long serialVersionUID = 3884649425767533205L; public FileUtilException(Throwable cause) { super(cause); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/Generator.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.security.MessageDigest; public final class Generator { private static final char[] CHARACTERS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; private static final String FONT_FAMILY_TIMES_NEW_ROMAN = "Times New Roman"; /** * md5 file * * @param fullFilename * @return String */ public static String md5File(String fullFilename) { String result = null; if (fullFilename != null) { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(fullFilename)); result = md5File(is); } catch (Exception e) { throw new RuntimeException(e); } finally { IOHelper.closeQuietly(is); } } return result; } /** * md5 file * * @param inputStream * @return String */ public static String md5File(final InputStream inputStream) { String result = null; if (inputStream != null) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] buffer = new byte[Constant.Capacity.BYTES_PER_KB]; int readCount = 0; while ((readCount = inputStream.read(buffer, 0, buffer.length)) != -1) { md.update(buffer, 0, readCount); } result = StringUtil.byteToHexString(md.digest()); } catch (Exception e) { e.printStackTrace(); } finally { IOHelper.closeQuietly(inputStream); } } return result; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/JavaXmlUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.tencent.tinker.commons.util.IOHelper; import org.w3c.dom.Document; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; public final class JavaXmlUtil { /** * get document builder * * @return DocumentBuilder */ private static DocumentBuilder getDocumentBuilder() { DocumentBuilder documentBuilder = null; DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); try { documentBuilder = documentBuilderFactory.newDocumentBuilder(); // Block any external content resolving actions since we don't need them and a report // says these actions may cause security problems. documentBuilder.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(); } }); } catch (Exception e) { throw new JavaXmlUtilException(e); } return documentBuilder; } public static Document getEmptyDocument() { Document document = null; try { DocumentBuilder documentBuilder = getDocumentBuilder(); document = documentBuilder.newDocument(); document.normalize(); } catch (Exception e) { throw new JavaXmlUtilException(e); } return document; } /** * parse * * @param filename * @return Document */ public static Document parse(final String filename) { Document document = null; try { DocumentBuilder documentBuilder = getDocumentBuilder(); document = documentBuilder.parse(new File(filename)); document.normalize(); } catch (Exception e) { throw new JavaXmlUtilException(e); } return document; } /** * parse * * @param inputStream * @return Document */ public static Document parse(final InputStream inputStream) { Document document = null; try { DocumentBuilder documentBuilder = getDocumentBuilder(); document = documentBuilder.parse(inputStream); document.normalize(); } catch (Exception e) { throw new JavaXmlUtilException(e); } return document; } /** * save document * * @param document * @param outputFullFilename */ public static void saveDocument(final Document document, final String outputFullFilename) { OutputStream outputStream = null; try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource domSource = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, Constant.Encoding.UTF8); outputStream = new FileOutputStream(outputFullFilename); StreamResult result = new StreamResult(outputStream); transformer.transform(domSource, result); } catch (Exception e) { throw new JavaXmlUtilException(e); } finally { IOHelper.closeQuietly(outputStream); } } public static class JavaXmlUtilException extends RuntimeException { private static final long serialVersionUID = 4669527982017700891L; public JavaXmlUtilException(Throwable cause) { super(cause); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ObjectUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; /** * reflect the object property and invoke the method * * @author Dandelion * @since 2008-04-?? */ public final class ObjectUtil { private ObjectUtil() { } /** * when object is null return blank,when the object is not null it return object; * * @param object * @return Object */ public static Object nullToBlank(Object object) { if (object == null) { return StringUtil.BLANK; } return object; } /** * equal * * @param a * @param b * @return boolean */ public static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } /** * field name to method name * * @param methodPrefix * @param fieldName * @return methodName */ public static String fieldNameToMethodName(String methodPrefix, String fieldName) { return fieldNameToMethodName(methodPrefix, fieldName, false); } /** * field name to method name * * @param methodPrefix * @param fieldName * @param ignoreFirstLetterCase * @return methodName */ public static String fieldNameToMethodName(String methodPrefix, String fieldName, boolean ignoreFirstLetterCase) { String methodName = null; if (fieldName != null && fieldName.length() > 0) { if (ignoreFirstLetterCase) { methodName = methodPrefix + fieldName; } else { methodName = methodPrefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); } } else { methodName = methodPrefix; } return methodName; } /** * method name to field name * * @param methodPrefix * @param methodName * @return fieldName */ public static String methodNameToFieldName(String methodPrefix, String methodName) { return methodNameToFieldName(methodPrefix, methodName, false); } /** * method name to field name * * @param methodPrefix * @param methodName * @param ignoreFirstLetterCase * @return fieldName */ public static String methodNameToFieldName(String methodPrefix, String methodName, boolean ignoreFirstLetterCase) { String fieldName = null; if (methodName != null && methodName.length() > methodPrefix.length()) { int front = methodPrefix.length(); if (ignoreFirstLetterCase) { fieldName = methodName.substring(front, front + 1) + methodName.substring(front + 1); } else { fieldName = methodName.substring(front, front + 1).toLowerCase() + methodName.substring(front + 1); } } return fieldName; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/PatchUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.tencent.tinker.build.aapt.RDotTxtEntry.IdType; import com.tencent.tinker.build.aapt.RDotTxtEntry.RType; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class PatchUtil { /** * read r txt * * @param rTxtFullFilename * @return Map> */ public static Map> readRTxt(String rTxtFullFilename) { //read base resource entry Map> rTypeResourceMap = new HashMap>(); if (StringUtil.isNotBlank(rTxtFullFilename) && FileUtil.isExist(rTxtFullFilename)) { BufferedReader bufferedReader = null; try { final Pattern textSymbolLine = Pattern.compile("(\\S+) (\\S+) (\\S+) (.+)"); bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(rTxtFullFilename))); String line = null; while ((line = bufferedReader.readLine()) != null) { Matcher matcher = textSymbolLine.matcher(line); if (matcher.matches()) { IdType idType = IdType.from(matcher.group(1)); RType rType = RType.valueOf(matcher.group(2).toUpperCase()); String name = matcher.group(3); String idValue = matcher.group(4); RDotTxtEntry rDotTxtEntry = new RDotTxtEntry(idType, rType, name, idValue); Set hashSet = null; if (rTypeResourceMap.containsKey(rType)) { hashSet = rTypeResourceMap.get(rType); } else { hashSet = new HashSet(); rTypeResourceMap.put(rType, hashSet); } hashSet.add(rDotTxtEntry); } } } catch (Exception e) { e.printStackTrace(); } finally { IOHelper.closeQuietly(bufferedReader); } } return rTypeResourceMap; } /** * generate public resource xml * * @param aaptResourceCollector * @param outputIdsXmlFullFilename * @param outputPublicXmlFullFilename */ public static void generatePublicResourceXml(AaptResourceCollector aaptResourceCollector, String outputIdsXmlFullFilename, String outputPublicXmlFullFilename) { if (aaptResourceCollector == null) { return; } FileUtil.createFile(outputIdsXmlFullFilename); FileUtil.createFile(outputPublicXmlFullFilename); PrintWriter idsWriter = null; PrintWriter publicWriter = null; try { FileUtil.createFile(outputIdsXmlFullFilename); FileUtil.createFile(outputPublicXmlFullFilename); idsWriter = new PrintWriter(new File(outputIdsXmlFullFilename), "UTF-8"); publicWriter = new PrintWriter(new File(outputPublicXmlFullFilename), "UTF-8"); idsWriter.println(""); publicWriter.println(""); idsWriter.println(""); publicWriter.println(""); Map> map = aaptResourceCollector.getRTypeResourceMap(); Iterator>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); RType rType = entry.getKey(); if (!rType.equals(RType.STYLEABLE)) { Set set = entry.getValue(); for (RDotTxtEntry rDotTxtEntry : set) { // if (rType.equals(RType.STYLE)) { String rawName = aaptResourceCollector.getRawName(rType, rDotTxtEntry.name); if (StringUtil.isBlank(rawName)) { // System.err.println("Blank?" + rDotTxtEntry.name); rawName = rDotTxtEntry.name; } publicWriter.println(""); // } else { // publicWriter.println(""); // } } Set ignoreIdSet = aaptResourceCollector.getIgnoreIdSet(); for (RDotTxtEntry rDotTxtEntry : set) { if (rType.equals(RType.ID) && !ignoreIdSet.contains(rDotTxtEntry.name)) { idsWriter.println(""); } else if (rType.equals(RType.STYLE)) { if (rDotTxtEntry.name.indexOf(Constant.Symbol.UNDERLINE) > 0) { // idsWriter.println(""); } } } } idsWriter.flush(); publicWriter.flush(); } idsWriter.println(""); publicWriter.println(""); } catch (Exception e) { throw new PatchUtilException(e); } finally { if (idsWriter != null) { idsWriter.flush(); idsWriter.close(); } if (publicWriter != null) { publicWriter.flush(); publicWriter.close(); } } } public static class PublicResourceEntry { private RType rType = null; private String resourceName = null; public PublicResourceEntry(RType rType, String resourceName) { this.rType = rType; this.resourceName = resourceName; } public boolean equals(Object obj) { if (!(obj instanceof PublicResourceEntry)) { return false; } PublicResourceEntry that = (PublicResourceEntry) obj; return ObjectUtil.equal(this.rType, that.rType) && ObjectUtil.equal(this.resourceName, that.resourceName); } public int hashCode() { return Arrays.hashCode(new Object[]{this.rType, this.resourceName}); } } public static class PatchUtilException extends RuntimeException { private static final long serialVersionUID = 5982003304074821184L; public PatchUtilException(String message) { super(message); } public PatchUtilException(Throwable cause) { super(cause); } public PatchUtilException(String message, Throwable cause) { super(message, cause); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/RDotTxtEntry.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ComparisonChain; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Represents a row from a symbols file generated by {@code aapt}. */ public class RDotTxtEntry implements Comparable { private static final Pattern TEXT_SYMBOLS_LINE = Pattern.compile("(\\S+) (\\S+) (\\S+) (.+)"); public static final Function TO_ENTRY = new Function() { public RDotTxtEntry apply(String input) { Optional entry = parse(input); Preconditions.checkNotNull(entry.isPresent(), "Could not parse R.txt entry: '%s'", input); return entry.get(); } }; // A symbols file may look like: // // int id placeholder 0x7f020000 // int string debug_http_proxy_dialog_title 0x7f030004 // int string debug_http_proxy_hint 0x7f030005 // int string debug_http_proxy_summary 0x7f030003 // int string debug_http_proxy_title 0x7f030002 // int string debug_ssl_cert_check_summary 0x7f030001 // int string debug_ssl_cert_check_title 0x7f030000 // // Note that there are four columns of information: // - the type of the resource id (always seems to be int or int[], in // practice) // - the type of the resource // - the name of the resource // - the value of the resource id public final IdType idType; public final RType type; public final String name; public String idValue; public RDotTxtEntry(IdType idType, RType type, String name, String idValue) { this.idType = Preconditions.checkNotNull(idType); this.type = Preconditions.checkNotNull(type); this.name = Preconditions.checkNotNull(name); this.idValue = Preconditions.checkNotNull(idValue); } public static Optional parse(String rDotTxtLine) { Matcher matcher = TEXT_SYMBOLS_LINE.matcher(rDotTxtLine); if (!matcher.matches()) { return Optional.absent(); } IdType idType = IdType.from(matcher.group(1)); RType type = RType.valueOf(matcher.group(2).toUpperCase()); String name = matcher.group(3); String idValue = matcher.group(4); return Optional.of(new RDotTxtEntry(idType, type, name, idValue)); } public RDotTxtEntry copyWithNewIdValue(String newIdValue) { return new RDotTxtEntry(idType, type, name, newIdValue); } /** * A collection of Resources should be sorted such that Resources of the * same type should be grouped together, and should be alphabetized within * that group. */ public int compareTo(RDotTxtEntry that) { return ComparisonChain.start().compare(this.type, that.type).compare(this.name, that.name).result(); } @Override public boolean equals(Object obj) { if (!(obj instanceof RDotTxtEntry)) { return false; } RDotTxtEntry that = (RDotTxtEntry) obj; return Objects.equal(this.type, that.type) && Objects.equal(this.name, that.name); } @Override public int hashCode() { return Arrays.hashCode(new Object[]{type, name}); } @Override public String toString() { return Objects.toStringHelper(RDotTxtEntry.class).add("idType", idType).add("type", type).add("name", name).add("idValue", idValue.trim()).toString(); } // Taken from http://developer.android.com/reference/android/R.html // TRANSITION for api level 19 public enum RType { ANIM, ANIMATOR, ARRAY, ATTR, BOOL, COLOR, DIMEN, DRAWABLE, FONT, FRACTION, ID, INTEGER, INTERPOLATOR, LAYOUT, MENU, MIPMAP, PLURALS, RAW, STRING, STYLE, STYLEABLE, TRANSITION, XML, NAVIGATION; @Override public String toString() { return super.toString().toLowerCase(); } } public enum IdType { INT, INT_ARRAY; public static IdType from(String raw) { if (raw.equals("int")) { return INT; } else if (raw.equals("int[]")) { return INT_ARRAY; } throw new IllegalArgumentException(String.format("'%s' is not a valid ID type.", raw)); } public String toString() { if (this.equals(INT)) { return "int"; } return "int[]"; } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceDirectory.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class ResourceDirectory { public String directoryName = null; public String resourceFullFilename = null; public Set resourceEntrySet = new HashSet(); public ResourceDirectory(String directoryName, String resourceFullFilename) { this.directoryName = directoryName; this.resourceFullFilename = resourceFullFilename; } public int hashCode() { return Arrays.hashCode(new Object[]{this.directoryName, this.resourceFullFilename}); } public boolean equals(Object object) { if (!(object instanceof ResourceDirectory)) { return false; } ResourceDirectory that = (ResourceDirectory) object; return ObjectUtil.equal(this.directoryName, that.directoryName) && ObjectUtil.equal(this.resourceFullFilename, that.resourceFullFilename); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/ResourceEntry.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import java.util.Arrays; public class ResourceEntry { public String name = null; public String value = null; public ResourceEntry(String name, String value) { this.name = name; this.value = value; } public int hashCode() { return Arrays.hashCode(new Object[]{this.name}); } public boolean equals(Object object) { if (!(object instanceof ResourceEntry)) { return false; } ResourceEntry that = (ResourceEntry) object; return ObjectUtil.equal(this.name, that.name); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/aapt/StringUtil.java ================================================ /* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.tencent.tinker.build.aapt; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class StringUtil { public static final String BLANK = ""; public static final String SPACE = " "; public static final String NULL = "null"; public static final String CRLF_STRING = "\r\n"; public static final byte CR = '\r'; public static final byte LF = '\n'; public static final byte[] CRLF = {CR, LF}; private static final String METCH_PATTERN_REGEX = "[\\*]+"; private static final String METCH_PATTERN = Constant.Symbol.WILDCARD; private static final String METCH_PATTERN_REPLACEMENT = "[\\\\S|\\\\s]*"; private static final String ZERO = "0"; private StringUtil() { } /** * when string is null return blank,where the string is not null it return string.trim * * @param string * @return String */ public static String trim(final String string) { String result = null; if (string == null) { result = BLANK; } else { result = string.trim(); } return result; } /** * when string is null return blank string * * @param string * @return String */ public static String nullToBlank(final String string) { return string == null ? BLANK : string; } /** * when string[] is null return blank array * * @param stringArray * @return String[]{} length==0 */ public static String[] nullToBlank(final String[] stringArray) { String[] result = stringArray; if (stringArray == null) { result = new String[]{}; } return result; } /** *

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

*

*

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

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

*

*

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

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

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

Method:only for regex

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

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

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

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

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

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

* * @param string * @param mod * @return int */ public static int stringMod(String string, int mod) { int hashCode = 0; if (string != null) { hashCode = string.hashCode(); if (hashCode < 0) { hashCode = Math.abs(hashCode); hashCode = hashCode < 0 ? 0 : hashCode; } } return hashCode % (mod > 0 ? mod : 1); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/apkparser/AndroidParser.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.apkparser; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.commons.util.IOHelper; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import tinker.net.dongliu.apk.parser.ApkParser; import tinker.net.dongliu.apk.parser.bean.ApkMeta; import tinker.net.dongliu.apk.parser.exception.ParserException; import tinker.net.dongliu.apk.parser.parser.ApkMetaTranslator; import tinker.net.dongliu.apk.parser.parser.BinaryXmlParser; import tinker.net.dongliu.apk.parser.parser.CompositeXmlStreamer; import tinker.net.dongliu.apk.parser.parser.ResourceTableParser; import tinker.net.dongliu.apk.parser.parser.XmlTranslator; import tinker.net.dongliu.apk.parser.struct.AndroidConstants; import tinker.net.dongliu.apk.parser.struct.ResourceValue; import tinker.net.dongliu.apk.parser.struct.StringPool; import tinker.net.dongliu.apk.parser.struct.resource.ResourceTable; import tinker.net.dongliu.apk.parser.struct.xml.Attribute; import tinker.net.dongliu.apk.parser.utils.ParseUtils; import tinker.net.dongliu.apk.parser.utils.Utils; /** * Created by zhangshaowen on 16/5/5. */ public class AndroidParser { public static final int TYPE_SERVICE = 1; public static final int TYPE_ACTIVITY = 2; public static final int TYPE_BROADCAST_RECEIVER = 3; public static final int TYPE_CONTENT_PROVIDER = 4; public final List activities = new ArrayList<>(); public final List receivers = new ArrayList<>(); public final List services = new ArrayList<>(); public final List providers = new ArrayList<>(); public final ApkMeta apkMeta; public final String xml; public final HashMap metaDatas = new HashMap<>(); public AndroidParser(ApkMeta apkMeta, String xml) throws ParserException { this.apkMeta = apkMeta; this.xml = xml; parse(); } public static boolean resourceTableLogicalChange(Configuration config) throws IOException { ApkParser parser = new ApkParser(config.mOldApkFile); ApkParser newParser = new ApkParser(config.mNewApkFile); parser.parseResourceTable(); newParser.parseResourceTable(); return parser.getResourceTable().equals(newParser.getResourceTable()); } public static void editResourceTableString(String from, String to, File originFile, File destFile) throws IOException { if (from == null || to == null) { return; } if (!originFile.exists()) { throw new RuntimeException("origin resources.arsc is not exist, path:" + originFile.getPath()); } if (from.length() != to.length()) { throw new RuntimeException("only support the same string length now!"); } ApkParser parser = new ApkParser(); parser.parseResourceTable(originFile); ResourceTable resourceTable = parser.getResourceTable(); StringPool stringPool = resourceTable.getStringPool(); ByteBuffer buffer = resourceTable.getBuffers(); byte[] array = buffer.array(); int length = stringPool.getPool().length; boolean found = false; for (int i = 0; i < length; i++) { String value = stringPool.get(i); if (value.equals(from)) { found = true; long offset = stringPool.getPoolOffsets().get(i); //length offset += 2; byte[] tempByte; if (stringPool.isUtf8()) { tempByte = to.getBytes(ParseUtils.charsetUTF8); if (to.length() != tempByte.length) { throw new RuntimeException(String.format( "editResourceTableString length is different, name %d, tempByte %d\n", to.length(), tempByte.length)); } } else { tempByte = to.getBytes(ParseUtils.charsetUTF16); if ((to.length() * 2) != tempByte.length) { throw new RuntimeException(String.format( "editResourceTableString length is different, name %d, tempByte %d\n", to.length(), tempByte.length)); } } System.arraycopy(tempByte, 0, array, (int) offset, tempByte.length); } } if (!found) { throw new RuntimeException("can't found string:" + from + " in the resources.arsc file's string pool!"); } //write array to file FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(destFile); fileOutputStream.write(array); } finally { IOHelper.closeQuietly(fileOutputStream); } } public static AndroidParser getAndroidManifest(File file) throws IOException, ParseException { ZipFile zf = null; try { zf = new ZipFile(file); final ByteBuffer arscData = getZipEntryData(zf, AndroidConstants.RESOURCE_FILE); final ResourceTableParser resTableParser = new ResourceTableParser(arscData); resTableParser.parse(); final ResourceTable resTable = resTableParser.getResourceTable(); final ByteBuffer manifestData = getZipEntryData(zf, AndroidConstants.MANIFEST_FILE); final BinaryXmlParser xmlParser = new BinaryXmlParser(manifestData, resTable); final ApkMetaTranslator metaTranslator = new ApkMetaTranslator(); final XmlTranslatorForPatch xmlTranslator = new XmlTranslatorForPatch(); final CompositeXmlStreamer compositeStreamer = new CompositeXmlStreamer(metaTranslator, xmlTranslator); xmlParser.setXmlStreamer(compositeStreamer); xmlParser.parse(); AndroidParser androidManifest = new AndroidParser(metaTranslator.getApkMeta(), xmlTranslator.getXml()); return androidManifest; } finally { if (zf != null) { try { zf.close(); } catch (Throwable ignored) { // Ignored. } } } } private static ByteBuffer getZipEntryData(ZipFile zf, String entryPath) throws IOException { final ZipEntry entry = zf.getEntry(entryPath); InputStream is = null; try { is = new BufferedInputStream(zf.getInputStream(entry)); final byte[] data = Utils.toByteArray(is); return ByteBuffer.wrap(data); } finally { if (is != null) { try { is.close(); } catch (Throwable ignored) { // Ignored. } } } } private static final class XmlTranslatorForPatch extends XmlTranslator { @Override public void onAttribute(Attribute attribute) { final ResourceValue attrVal = attribute.getTypedValue(); if (attrVal != null && attrVal instanceof ResourceValue.ReferenceResourceValue) { attribute.setValue(attrVal.toString()); } super.onAttribute(attribute); } } private static String getAttribute(NamedNodeMap namedNodeMap, String name) { Node node = namedNodeMap.getNamedItem(name); if (node == null) { if (name.startsWith("android:")) { name = name.substring("android:".length()); } node = namedNodeMap.getNamedItem(name); if (node == null) { return null; } } return node.getNodeValue(); } /** * @return a list of all components */ public List getComponents() { List components = new ArrayList<>(); components.addAll(activities); components.addAll(services); components.addAll(receivers); components.addAll(providers); return components; } private void parse() throws ParserException { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); Document document; try { DocumentBuilder builder = builderFactory.newDocumentBuilder(); // Block any external content resolving actions since we don't need them and a report // says these actions may cause security problems. builder.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(); } }); document = builder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))); Node manifestNode = document.getElementsByTagName("manifest").item(0); NodeList nodes = manifestNode.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); String nodeName = node.getNodeName(); if (nodeName.equals("application")) { NodeList children = node.getChildNodes(); for (int j = 0; j < children.getLength(); j++) { Node child = children.item(j); String childName = child.getNodeName(); switch (childName) { case "service": services.add(getAndroidComponent(child, TYPE_SERVICE)); break; case "activity": activities.add(getAndroidComponent(child, TYPE_ACTIVITY)); break; case "receiver": receivers.add(getAndroidComponent(child, TYPE_BROADCAST_RECEIVER)); break; case "provider": providers.add(getAndroidComponent(child, TYPE_CONTENT_PROVIDER)); break; case "meta-data": NamedNodeMap attributes = child.getAttributes(); metaDatas.put(getAttribute(attributes, "android:name"), getAttribute(attributes, "android:value")); break; default: break; } } } } } catch (Exception e) { throw new ParserException("Error parsing AndroidManifest.xml", e); } } private String getAndroidComponent(Node node, int type) { NamedNodeMap attributes = node.getAttributes(); return getAttribute(attributes, "android:name"); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/builder/PatchBuilder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.builder; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.Key; import java.security.KeyStore; import java.util.ArrayList; /** * @author zhangshaowen */ public class PatchBuilder { private static final String PATCH_NAME = "patch"; private final Configuration config; private File unSignedApk; private File signedApk; private File signedWith7ZipApk; private File sevenZipOutPutDir; public PatchBuilder(Configuration config) { this.config = config; this.unSignedApk = new File(config.mOutFolder, PATCH_NAME + "_unsigned.apk"); this.signedApk = new File(config.mOutFolder, PATCH_NAME + "_signed.apk"); this.signedWith7ZipApk = new File(config.mOutFolder, PATCH_NAME + "_signed_7zip.apk"); this.sevenZipOutPutDir = new File(config.mOutFolder, TypedValue.OUT_7ZIP_FILE_PATH); } public void buildPatch() throws Exception { final File resultDir = config.mTempResultDir; if (!resultDir.exists()) { throw new IOException(String.format( "Missing patch unzip files, path=%s\n", resultDir.getAbsolutePath())); } //no file change if (resultDir.listFiles().length == 0) { return; } generateUnsignedApk(unSignedApk); signApk(unSignedApk, signedApk); use7zApk(signedApk, signedWith7ZipApk, sevenZipOutPutDir); if (!signedApk.exists()) { Logger.e("Result: final unsigned patch result: %s, size=%d", unSignedApk.getAbsolutePath(), unSignedApk.length()); } else { long length = signedApk.length(); Logger.e("Result: final signed patch result: %s, size=%d", signedApk.getAbsolutePath(), length); if (signedWith7ZipApk.exists()) { long length7zip = signedWith7ZipApk.length(); Logger.e("Result: final signed with 7zip patch result: %s, size=%d", signedWith7ZipApk.getAbsolutePath(), length7zip); if (length7zip > length) { Logger.e("Warning: %s is bigger than %s %d byte, you should choose %s at these time!", signedWith7ZipApk.getName(), signedApk.getName(), (length7zip - length), signedApk.getName()); } } } } private String getSignatureAlgorithm() throws Exception { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(config.mSignatureFile)); KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(is, config.mStorePass.toCharArray()); Key key = keyStore.getKey(config.mStoreAlias, config.mKeyPass.toCharArray()); String keyAlgorithm = key.getAlgorithm(); String signatureAlgorithm; if (keyAlgorithm.equalsIgnoreCase("DSA")) { signatureAlgorithm = "SHA1withDSA"; } else if (keyAlgorithm.equalsIgnoreCase("RSA")) { signatureAlgorithm = "SHA1withRSA"; } else if (keyAlgorithm.equalsIgnoreCase("EC")) { signatureAlgorithm = "SHA1withECDSA"; } else { throw new RuntimeException("private key is not a DSA or " + "RSA key"); } return signatureAlgorithm; } finally { IOHelper.closeQuietly(is); } } /** * @param input unsigned file input * @param output signed file output * @throws IOException * @throws InterruptedException */ private void signApk(File input, File output) throws Exception { //sign apk if (config.mUseSignAPk) { Logger.d("Signing apk: %s", output.getName()); String signatureAlgorithm = getSignatureAlgorithm(); Logger.d("Signing key algorithm is %s", signatureAlgorithm); if (output.exists()) { output.delete(); } ArrayList command = new ArrayList<>(); command.add("jarsigner"); // issue https://github.com/Tencent/tinker/issues/118 command.add("-sigalg"); command.add(signatureAlgorithm); command.add("-digestalg"); command.add("SHA1"); command.add("-keystore"); command.add(config.mSignatureFile.getAbsolutePath()); command.add("-storepass"); command.add(config.mStorePass); command.add("-keypass"); command.add(config.mKeyPass); command.add("-signedjar"); command.add(output.getAbsolutePath()); command.add(input.getAbsolutePath()); command.add(config.mStoreAlias); Process process = new ProcessBuilder(command).start(); process.waitFor(); process.destroy(); if (!output.exists()) { throw new IOException("Can't Generate signed APK. Please check if your sign info is correct."); } } } /** * @param output unsigned apk file output * @throws IOException */ private void generateUnsignedApk(File output) throws IOException { Logger.d("Generate unsigned apk: %s", output.getName()); final File tempOutDir = config.mTempResultDir; if (!tempOutDir.exists()) { throw new IOException(String.format( "Missing patch unzip files, path=%s\n", tempOutDir.getAbsolutePath())); } FileOperation.zipInputDir(tempOutDir, output, null); if (!output.exists()) { throw new IOException(String.format( "can not found the unsigned apk file path=%s", output.getAbsolutePath())); } } private void use7zApk(File inputSignedFile, File out7zipFile, File tempFilesDir) throws IOException { if (!config.mUseSignAPk) { return; } if (!inputSignedFile.exists()) { throw new IOException( String.format("can not found the signed apk file to 7z, if you want to use 7z, " + "you must fill the sign data in the config file path=%s", inputSignedFile.getAbsolutePath()) ); } Logger.d("Try use 7za to compress the patch file: %s, will cost much more time", out7zipFile.getName()); Logger.d("Current 7za path:%s", config.mSevenZipPath); FileOperation.unZipAPk(inputSignedFile.getAbsolutePath(), tempFilesDir.getAbsolutePath()); //7zip may not enable if (!FileOperation.sevenZipInputDir(tempFilesDir, out7zipFile, config)) { return; } FileOperation.deleteDir(tempFilesDir); if (!out7zipFile.exists()) { throw new IOException(String.format( "[use7zApk]7z repackage signed apk fail,you must install 7z command line version first, linux: p7zip, window: 7za, path=%s", out7zipFile.getAbsolutePath())); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ApkDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.MD5; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.build.util.Utils; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.regex.Matcher; /** * Created by zhangshaowen on 16/3/15. */ public class ApkDecoder extends BaseDecoder { private final File mOldApkDir; private final File mNewApkDir; private final ManifestDecoder manifestDecoder; private final UniqueDexDiffDecoder dexPatchDecoder; private final SoDiffDecoder soPatchDecoder; private final ResDiffDecoder resPatchDecoder; private final ArkHotDecoder arkHotDecoder; /** * if resource's file is also contain in dex or library pattern, * they won't change in new resources' apk, and we will just warn you. */ ArrayList resDuplicateFiles; public ApkDecoder(Configuration config) throws IOException { super(config); this.mNewApkDir = config.mTempUnzipNewDir; this.mOldApkDir = config.mTempUnzipOldDir; this.manifestDecoder = new ManifestDecoder(config); //put meta files in assets String prePath = TypedValue.FILE_ASSETS + File.separator; dexPatchDecoder = new UniqueDexDiffDecoder(config, prePath + TypedValue.DEX_META_FILE, TypedValue.DEX_LOG_FILE); soPatchDecoder = new SoDiffDecoder(config, prePath + TypedValue.SO_META_FILE, TypedValue.SO_LOG_FILE); resPatchDecoder = new ResDiffDecoder(config, prePath + TypedValue.RES_META_TXT, TypedValue.RES_LOG_FILE); arkHotDecoder = new ArkHotDecoder(config, prePath + TypedValue.ARKHOT_META_TXT); Logger.d("config: " + config.mArkHotPatchPath + " " + config.mArkHotPatchName + prePath + TypedValue.ARKHOT_META_TXT); resDuplicateFiles = new ArrayList<>(); } private void unzipApkFile(File file, File destFile) throws TinkerPatchException, IOException { String apkName = file.getName(); if (!apkName.endsWith(TypedValue.FILE_APK)) { throw new TinkerPatchException( String.format("input apk file path must end with .apk, yours %s\n", apkName) ); } String destPath = destFile.getAbsolutePath(); Logger.d("UnZipping apk to %s", destPath); FileOperation.unZipAPk(file.getAbsoluteFile().getAbsolutePath(), destPath); } private void unzipApkFiles(File oldFile, File newFile) throws IOException, TinkerPatchException { unzipApkFile(oldFile, this.mOldApkDir); unzipApkFile(newFile, this.mNewApkDir); } private void writeToLogFile(File oldFile, File newFile) throws IOException { String line1 = "old apk: " + oldFile.getName() + ", size=" + FileOperation.getFileSizes(oldFile) + ", md5=" + MD5.getMD5(oldFile); String line2 = "new apk: " + newFile.getName() + ", size=" + FileOperation.getFileSizes(newFile) + ", md5=" + MD5.getMD5(newFile); Logger.d("Analyze old and new apk files1:"); Logger.d(line1); Logger.d(line2); Logger.d(""); } @Override public void onAllPatchesStart() throws IOException, TinkerPatchException { manifestDecoder.onAllPatchesStart(); dexPatchDecoder.onAllPatchesStart(); soPatchDecoder.onAllPatchesStart(); resPatchDecoder.onAllPatchesStart(); } public boolean patch(File oldFile, File newFile) throws Exception { writeToLogFile(oldFile, newFile); //check manifest change first manifestDecoder.patch(oldFile, newFile); unzipApkFiles(oldFile, newFile); Files.walkFileTree(mNewApkDir.toPath(), new ApkFilesVisitor(config, mNewApkDir.toPath(), mOldApkDir.toPath(), dexPatchDecoder, soPatchDecoder, resPatchDecoder)); // get all duplicate resource file for (File duplicateRes : resDuplicateFiles) { // resPatchDecoder.patch(duplicateRes, null); Logger.e("Warning: res file %s is also match at dex or library pattern, " + "we treat it as unchanged in the new resource_out.zip", getRelativePathStringToOldFile(duplicateRes)); } soPatchDecoder.onAllPatchesEnd(); dexPatchDecoder.onAllPatchesEnd(); manifestDecoder.onAllPatchesEnd(); resPatchDecoder.onAllPatchesEnd(); arkHotDecoder.onAllPatchesEnd(); //clean resources dexPatchDecoder.clean(); soPatchDecoder.clean(); resPatchDecoder.clean(); arkHotDecoder.clean(); return true; } @Override public void onAllPatchesEnd() throws IOException, TinkerPatchException { } class ApkFilesVisitor extends SimpleFileVisitor { BaseDecoder dexDecoder; BaseDecoder soDecoder; BaseDecoder resDecoder; Configuration config; Path newApkPath; Path oldApkPath; ApkFilesVisitor(Configuration config, Path newPath, Path oldPath, BaseDecoder dex, BaseDecoder so, BaseDecoder resDecoder) { this.config = config; this.dexDecoder = dex; this.soDecoder = so; this.resDecoder = resDecoder; this.newApkPath = newPath; this.oldApkPath = oldPath; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativePath = newApkPath.relativize(file); Path oldPath = oldApkPath.resolve(relativePath); File oldFile = null; //is a new file?! if (oldPath.toFile().exists()) { oldFile = oldPath.toFile(); } String patternKey = relativePath.toString().replace("\\", "/"); if (Utils.checkFileInPattern(config.mDexFilePattern, patternKey)) { //also treat duplicate file as unchanged if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { resDuplicateFiles.add(oldFile); } try { dexDecoder.patch(oldFile, file.toFile()); } catch (Exception e) { throw new RuntimeException(e); } return FileVisitResult.CONTINUE; } if (Utils.checkFileInPattern(config.mSoFilePattern, patternKey)) { //also treat duplicate file as unchanged if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { resDuplicateFiles.add(oldFile); } // For abi validation purpose. if (file.toFile().exists()) { final String newAbi = getAbiFromPath(file.toFile().getAbsolutePath()); if (newAbi != null) { final File oldSoPathWithNewAbi = new File(oldApkPath.toFile(), "lib/" + newAbi); if (!oldSoPathWithNewAbi.exists()) { throw new UnsupportedOperationException("Tinker does not support to add new ABI: " + newAbi + ", related new so: " + file.toFile().getAbsolutePath()); } } } try { soDecoder.patch(oldFile, file.toFile()); } catch (Exception e) { throw new RuntimeException(e); } return FileVisitResult.CONTINUE; } if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) { try { resDecoder.patch(oldFile, file.toFile()); } catch (Exception e) { throw new RuntimeException(e); } return FileVisitResult.CONTINUE; } return FileVisitResult.CONTINUE; } private String getAbiFromPath(String path) { path = path.replaceAll(Matcher.quoteReplacement(File.separator), "/"); final int prefixPos = path.indexOf("/lib/"); if (prefixPos < 0) { return null; } final int suffixPos = path.indexOf("/", prefixPos + 5); if (suffixPos < 0) { return null; } return path.substring(prefixPos + 5, suffixPos); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ArkHotDecoder.java ================================================ /* * Copyright (C) 2019. Huawei Technologies Co., Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD 3-Clause License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * the BSD 3-Clause License for more details. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.info.InfoWriter; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.MD5; import com.tencent.tinker.build.util.TinkerPatchException; import java.io.File; import java.io.IOException; public class ArkHotDecoder extends BaseDecoder { private static final String ARKHOT_PATCH_NAME = "patch.apk"; private static final String ARKHOT_PATCH_PATH = "arkHot"; private final InfoWriter metaWriter; public ArkHotDecoder(Configuration config, String metaPath) throws IOException { super(config); if (metaPath != null) { metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); } else { metaWriter = null; } } @Override public void clean() { metaWriter.close(); } @Override public void onAllPatchesStart() { } @Override public void onAllPatchesEnd() throws IOException, TinkerPatchException { File patchFile = new File(config.mArkHotPatchPath + "/" + config.mArkHotPatchName); if (!patchFile.exists()) { return; } String md5 = MD5.getMD5(patchFile); File dest = new File(config.mTempResultDir + "/" + ARKHOT_PATCH_PATH + "/" + ARKHOT_PATCH_NAME); FileOperation.copyFileUsingStream(patchFile, dest); writeMetaFile(md5); } @Override public boolean patch(File oldFile, File newFile) { return true; } private void writeMetaFile(String md5) { if (metaWriter == null) { return; } if (metaWriter != null) { String path = ARKHOT_PATCH_PATH; String fileName = ARKHOT_PATCH_NAME; if (md5 == null) { return; } String meta = fileName + "," + path + "," + md5; metaWriter.writeLineToInfoFile(meta); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/BaseDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.TinkerPatchException; import java.io.File; import java.io.IOException; import java.nio.file.Path; /** * Created by zhangshaowen on 16/2/28. */ public abstract class BaseDecoder { protected final Configuration config; protected final File outDir; protected final File resultDir; public BaseDecoder(Configuration config) throws IOException { this.config = config; this.outDir = new File(config.mOutFolder); this.resultDir = config.mTempResultDir; } public Configuration getConfig() { return config; } protected void clean() { } public Path getRelativePath(File file) { return config.mTempUnzipNewDir.toPath().relativize(file.toPath()); } public Path getOutputPath(File file) { return config.mTempResultDir.toPath().resolve(getRelativePath(file)); } public String getRelativePathStringToOldFile(File oldFile) { return config.mTempUnzipOldDir.toPath().relativize(oldFile.toPath()).toString().replace("\\", "/"); } public String getRelativePathStringToNewFile(File newFile) { return config.mTempUnzipNewDir.toPath().relativize(newFile.toPath()).toString().replace("\\", "/"); } public String getParentRelativePathStringToNewFile(File newFile) { return config.mTempUnzipNewDir.toPath().relativize(newFile.getParentFile().toPath()).toString().replace("\\", "/"); } /** * 就算前后两个文件都是一样,也会交到这个文件夹 * * @param oldFile 如果oldfile 为空,代表这是一个新的文件 * @param newFile * @throws IOException * @throws TinkerPatchException */ abstract public boolean patch(File oldFile, File newFile) throws Exception; abstract public void onAllPatchesStart() throws Exception; abstract public void onAllPatchesEnd() throws Exception; } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/DexDiffDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.DexFormat; import com.tencent.tinker.build.dexpatcher.DexPatchGenerator; import com.tencent.tinker.build.dexpatcher.util.ChangedClassesDexClassInfoCollector; import com.tencent.tinker.build.dexpatcher.util.PatternUtils; import com.tencent.tinker.build.info.InfoWriter; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.DexClassesComparator; import com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; import com.tencent.tinker.build.util.DexClassesComparator.DexGroup; import com.tencent.tinker.build.util.ExcludedClassModifiedChecker; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.MD5; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.build.util.Utils; import com.tencent.tinker.commons.dexpatcher.DexPatchApplier; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger.IDexPatcherLogger; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.builder.BuilderMutableMethodImplementation; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.Field; import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.MethodImplementation; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.ReferenceInstruction; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.iface.reference.TypeReference; import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.TypeUtils; import org.jf.dexlib2.writer.builder.BuilderField; import org.jf.dexlib2.writer.builder.BuilderMethod; import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.dexlib2.writer.io.FileDataStore; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.regex.Pattern; import java.util.zip.ZipEntry; /** * Created by zhangshaowen on 2016/3/23. */ public class DexDiffDecoder extends BaseDecoder { private static final String TEST_DEX_NAME = "test.dex"; private static final String CHANGED_CLASSES_DEX_NAME_PREFIX = "changed_classes"; private final InfoWriter logWriter; private final InfoWriter metaWriter; private final ExcludedClassModifiedChecker excludedClassModifiedChecker; private final Map addedClassDescToDexNameMap; private final Map deletedClassDescToDexNameMap; private final List> oldAndNewDexFilePairList; private final Map dexNameToRelatedInfoMap; private boolean hasDexChanged = false; private DexPatcherLoggerBridge dexPatcherLoggerBridge = null; private final Set loaderClassPatterns; private final Set descOfClassesInApk; private final Set descOfSyntheticClassesInApk; private final List oldDexFiles; public DexDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { super(config); if (metaPath != null) { metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); } else { metaWriter = null; } if (logPath != null) { logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); } else { logWriter = null; } if (logWriter != null) { this.dexPatcherLoggerBridge = new DexPatcherLoggerBridge(logWriter); } excludedClassModifiedChecker = new ExcludedClassModifiedChecker(config); addedClassDescToDexNameMap = new HashMap<>(); deletedClassDescToDexNameMap = new HashMap<>(); oldAndNewDexFilePairList = new ArrayList<>(); dexNameToRelatedInfoMap = new HashMap<>(); loaderClassPatterns = new HashSet<>(); for (String patternStr : config.mDexLoaderPattern) { loaderClassPatterns.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } descOfClassesInApk = new HashSet<>(); descOfSyntheticClassesInApk = new HashSet<>(); oldDexFiles = new ArrayList<>(); } @Override public void onAllPatchesStart() throws IOException, TinkerPatchException { descOfClassesInApk.clear(); descOfSyntheticClassesInApk.clear(); oldDexFiles.clear(); } /** * Provide /oldFileRoot/dir/to/oldDex, /newFileRoot/dir/to/newDex, * return dir/to/oldDex or dir/to/newDex if any one is not null. */ protected String getRelativeDexName(File oldDexFile, File newDexFile) { return oldDexFile != null ? getRelativePathStringToOldFile(oldDexFile) : getRelativePathStringToNewFile(newDexFile); } private void collectClassesInDex(File dexFile) throws IOException { Logger.d("Collect class descriptors in " + dexFile.getName()); final DexFile dex = DexFileFactory.loadDexFile(dexFile, Opcodes.forApi(29)); for (org.jf.dexlib2.iface.ClassDef classDef : dex.getClasses()) { descOfClassesInApk.add(classDef.getType()); if (AccessFlags.SYNTHETIC.isSet(classDef.getAccessFlags())) { descOfSyntheticClassesInApk.add(classDef.getType()); } } } @SuppressWarnings("NewApi") @Override public boolean patch(final File oldFile, final File newFile) throws IOException, TinkerPatchException { final String dexName = getRelativeDexName(oldFile, newFile); // first of all, we should check input files if excluded classes were modified. Logger.d("Check for loader classes in dex: %s", dexName); try { excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile, newFile); } catch (IOException e) { throw new TinkerPatchException(e); } catch (TinkerPatchException e) { if (config.mIgnoreWarning) { Logger.e("Warning:ignoreWarning is true, but we found %s", e.getMessage()); } else { Logger.e("Warning:ignoreWarning is false, but we found %s", e.getMessage()); throw e; } } catch (Exception e) { e.printStackTrace(); } // If corresponding new dex was completely deleted, just return false. // don't process 0 length dex if (newFile == null || !newFile.exists() || newFile.length() == 0) { return false; } File dexDiffOut = getOutputPath(newFile).toFile(); final String newMd5 = getRawOrWrappedDexMD5(newFile); //new add file if (oldFile == null || !oldFile.exists() || oldFile.length() == 0) { hasDexChanged = true; copyNewDexAndLogToDexMeta(newFile, newMd5, dexDiffOut); return true; } // Collect class descriptors here for further checking. collectClassesInDex(oldFile); oldDexFiles.add(oldFile); final String oldMd5 = getRawOrWrappedDexMD5(oldFile); if ((oldMd5 != null && !oldMd5.equals(newMd5)) || (oldMd5 == null && newMd5 != null)) { hasDexChanged = true; if (oldMd5 != null) { collectAddedOrDeletedClasses(oldFile, newFile); } } RelatedInfo relatedInfo = new RelatedInfo(); relatedInfo.oldMd5 = oldMd5; relatedInfo.newMd5 = newMd5; // collect current old dex file and corresponding new dex file for further processing. oldAndNewDexFilePairList.add(new AbstractMap.SimpleEntry<>(oldFile, newFile)); dexNameToRelatedInfoMap.put(dexName, relatedInfo); return true; } @Override public void onAllPatchesEnd() throws Exception { if (!hasDexChanged) { Logger.d("No dexes were changed, nothing needs to be done next."); return; } checkIfLoaderClassesReferToNonLoaderClasses(); if (config.mIsProtectedApp) { generateChangedClassesDexFile(); } else { generatePatchInfoFile(); } addTestDex(); } private boolean isReferenceFromLoaderClassValid(String refereeTypeDesc) { if (TypeUtils.isPrimitiveType(refereeTypeDesc)) { return true; } if (!descOfClassesInApk.contains(refereeTypeDesc)) { return true; } if (Utils.isStringMatchesPatterns(refereeTypeDesc, loaderClassPatterns)) { return true; } if (descOfSyntheticClassesInApk.contains(refereeTypeDesc)) { return true; } return false; } private void checkIfLoaderClassesReferToNonLoaderClasses() throws IOException, TinkerPatchException { boolean hasInvalidCases = false; for (File dexFile : oldDexFiles) { Logger.d("Check if loader classes in " + dexFile.getName() + " refer to any classes that is not in loader class patterns."); final DexFile dex = DexFileFactory.loadDexFile(dexFile, Opcodes.forApi(29)); for (org.jf.dexlib2.iface.ClassDef classDef : dex.getClasses()) { final String currClassDesc = classDef.getType(); if (!Utils.isStringMatchesPatterns(currClassDesc, loaderClassPatterns)) { continue; } for (Field field : classDef.getFields()) { final String currFieldTypeDesc = field.getType(); if (!isReferenceFromLoaderClassValid(currFieldTypeDesc)) { Logger.e("FATAL: field '%s' in loader class '%s' refers to class '%s' which " + "is not loader class, this may cause crash when patch is loaded.", field.getName(), currClassDesc, currFieldTypeDesc); hasInvalidCases = true; } } for (Method method : classDef.getMethods()) { boolean isCurrentMethodInvalid = false; final String currMethodRetTypeDesc = method.getReturnType(); if (!isReferenceFromLoaderClassValid(currMethodRetTypeDesc)) { Logger.e("FATAL: method '%s:%s' in loader class '%s' refers to class '%s' which " + "is not loader class, this may cause crash when patch is loaded.", method.getName(), MethodUtil.getShorty(method.getParameterTypes(), currMethodRetTypeDesc), currClassDesc, currMethodRetTypeDesc); isCurrentMethodInvalid = true; } else { for (CharSequence paramTypeDesc : method.getParameterTypes()) { if (!isReferenceFromLoaderClassValid(paramTypeDesc.toString())) { Logger.e("FATAL: method '%s:%s' in loader class '%s' refers to class '%s' which " + "is not loader class, this may cause crash when patch is loaded.", method.getName(), MethodUtil.getShorty(method.getParameterTypes(), currMethodRetTypeDesc), currClassDesc, paramTypeDesc); isCurrentMethodInvalid = true; break; } } } check_method_impl: { final MethodImplementation methodImpl = method.getImplementation(); if (methodImpl == null) { break check_method_impl; } final Iterable insns = methodImpl.getInstructions(); if (!insns.iterator().hasNext()) { break check_method_impl; } for (Instruction insn : insns) { if (insn instanceof ReferenceInstruction) { final ReferenceInstruction refInsn = (ReferenceInstruction) insn; switch (refInsn.getReferenceType()) { case ReferenceType.TYPE: { final TypeReference typeRefInsn = (TypeReference) refInsn.getReference(); final String refereeTypeDesc = typeRefInsn.getType(); if (isReferenceFromLoaderClassValid(refereeTypeDesc)) { break; } Logger.e("FATAL: method '%s:%s' in loader class '%s' refers to class '%s' which " + "is not loader class, this may cause crash when patch is loaded.", method.getName(), MethodUtil.getShorty(method.getParameterTypes(), currMethodRetTypeDesc), currClassDesc, refereeTypeDesc); isCurrentMethodInvalid = true; break; } case ReferenceType.FIELD: { final FieldReference fieldRefInsn = (FieldReference) refInsn.getReference(); final String refereeFieldName = fieldRefInsn.getName(); final String refereeFieldDefTypeDesc = fieldRefInsn.getDefiningClass(); if (isReferenceFromLoaderClassValid(refereeFieldDefTypeDesc)) { break; } Logger.e("FATAL: method '%s:%s' in loader class '%s' refers to field '%s' in class '%s' which " + "is not in loader class, this may cause crash when patch is loaded.", method.getName(), MethodUtil.getShorty(method.getParameterTypes(), currMethodRetTypeDesc), currClassDesc, refereeFieldName, refereeFieldDefTypeDesc); isCurrentMethodInvalid = true; break; } case ReferenceType.METHOD: { final MethodReference methodRefInsn = (MethodReference) refInsn.getReference(); final String refereeMethodName = methodRefInsn.getName(); final Collection refereeMethodParamTypes = methodRefInsn.getParameterTypes(); final String refereeMethodRetType = methodRefInsn.getReturnType(); final String refereeMethodDefClassDesc = methodRefInsn.getDefiningClass(); if (isReferenceFromLoaderClassValid(refereeMethodDefClassDesc)) { break; } Logger.e("FATAL: method '%s:%s' in loader class '%s' refers to method '%s:%s' in class '%s' which " + "is not in loader class, this may cause crash when patch is loaded.", method.getName(), MethodUtil.getShorty(method.getParameterTypes(), currMethodRetTypeDesc), currClassDesc, refereeMethodName, MethodUtil.getShorty(refereeMethodParamTypes, refereeMethodRetType), refereeMethodDefClassDesc); isCurrentMethodInvalid = true; break; } default: { break; } } } } } if (isCurrentMethodInvalid) { hasInvalidCases = true; } } } } if (hasInvalidCases) { throw new TinkerPatchException("There are fatal reasons that cause Tinker interrupt" + " patch generating procedure, see logs above."); } } @SuppressWarnings("NewApi") private void generateChangedClassesDexFile() throws IOException { final String dexMode = config.mDexRaw ? "raw" : "jar"; List oldDexList = new ArrayList<>(); List newDexList = new ArrayList<>(); for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { File oldDexFile = oldAndNewDexFilePair.getKey(); File newDexFile = oldAndNewDexFilePair.getValue(); if (oldDexFile != null) { oldDexList.add(oldDexFile); } if (newDexFile != null) { newDexList.add(newDexFile); } } DexGroup oldDexGroup = DexGroup.wrap(oldDexList); DexGroup newDexGroup = DexGroup.wrap(newDexList); ChangedClassesDexClassInfoCollector collector = new ChangedClassesDexClassInfoCollector(); collector.setExcludedClassPatterns(config.mDexLoaderPattern); collector.setLogger(dexPatcherLoggerBridge); collector.setIncludeRefererToRefererAffectedClasses(true); Set classInfosInChangedClassesDex = collector.doCollect(oldDexGroup, newDexGroup); Set owners = new HashSet<>(); Map> ownerToDescOfChangedClassesMap = new HashMap<>(); for (DexClassInfo classInfo : classInfosInChangedClassesDex) { owners.add(classInfo.owner); Set descOfChangedClasses = ownerToDescOfChangedClassesMap.get(classInfo.owner); if (descOfChangedClasses == null) { descOfChangedClasses = new HashSet<>(); ownerToDescOfChangedClassesMap.put(classInfo.owner, descOfChangedClasses); } descOfChangedClasses.add(classInfo.classDesc); } StringBuilder metaBuilder = new StringBuilder(); int changedDexId = 1; for (Dex dex : owners) { Set descOfChangedClassesInCurrDex = ownerToDescOfChangedClassesMap.get(dex); DexFile dexFile = new DexBackedDexFile(org.jf.dexlib2.Opcodes.forApi(20), dex.getBytes()); boolean isCurrentDexHasChangedClass = false; for (org.jf.dexlib2.iface.ClassDef classDef : dexFile.getClasses()) { if (descOfChangedClassesInCurrDex.contains(classDef.getType())) { isCurrentDexHasChangedClass = true; break; } } if (!isCurrentDexHasChangedClass) { continue; } DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(23)); for (org.jf.dexlib2.iface.ClassDef classDef : dexFile.getClasses()) { if (!descOfChangedClassesInCurrDex.contains(classDef.getType())) { continue; } Logger.d("Class %s will be added into changed classes dex ...", classDef.getType()); List builderFields = new ArrayList<>(); for (Field field : classDef.getFields()) { final BuilderField builderField = dexBuilder.internField( field.getDefiningClass(), field.getName(), field.getType(), field.getAccessFlags(), field.getInitialValue(), field.getAnnotations() ); builderFields.add(builderField); } List builderMethods = new ArrayList<>(); for (Method method : classDef.getMethods()) { MethodImplementation methodImpl = method.getImplementation(); if (methodImpl != null) { methodImpl = new BuilderMutableMethodImplementation(dexBuilder, methodImpl); } BuilderMethod builderMethod = dexBuilder.internMethod( method.getDefiningClass(), method.getName(), method.getParameters(), method.getReturnType(), method.getAccessFlags(), method.getAnnotations(), methodImpl ); builderMethods.add(builderMethod); } dexBuilder.internClassDef( classDef.getType(), classDef.getAccessFlags(), classDef.getSuperclass(), classDef.getInterfaces(), classDef.getSourceFile(), classDef.getAnnotations(), builderFields, builderMethods ); } // Write constructed changed classes dex to file and record it in meta file. String changedDexName = null; if (changedDexId == 1) { changedDexName = "classes.dex"; } else { changedDexName = "classes" + changedDexId + ".dex"; } final File dest = new File(config.mTempResultDir + "/" + changedDexName); final FileDataStore fileDataStore = new FileDataStore(dest); dexBuilder.writeTo(fileDataStore); final String md5 = MD5.getMD5(dest); appendMetaLine(metaBuilder, changedDexName, "", md5, md5, 0, 0, 0, dexMode); ++changedDexId; } final String meta = metaBuilder.toString(); Logger.d("\nDexDecoder:write changed classes dex meta file data:\n%s", meta); metaWriter.writeLineToInfoFile(meta); } private void appendMetaLine(StringBuilder sb, Object... vals) { if (vals == null || vals.length == 0) { return; } boolean isFirstItem = true; for (Object val : vals) { if (isFirstItem) { isFirstItem = false; } else { sb.append(','); } sb.append(val); } sb.append('\n'); } @SuppressWarnings("NewApi") private void generatePatchInfoFile() throws IOException { generatePatchedDexInfoFile(); // generateSmallPatchedDexInfoFile is blocked by issue we found in ART environment // which indicates that if inline optimization is done on patched class, some error // such as crash, ClassCastException, mistaken string fetching, etc. would happen. // // Instead, we will log all classN dexes as 'copy directly' in dex-meta, so that // tinker patch applying procedure will copy them out and load them in ART environment. //generateSmallPatchedDexInfoFile(); logDexesToDexMeta(); checkCrossDexMovingClasses(); } @SuppressWarnings("NewApi") private void logDexesToDexMeta() throws IOException { Map dexNameToClassNOldDexFileMap = new HashMap<>(); Set realClassNDexFiles = new HashSet<>(); for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { File oldFile = oldAndNewDexFilePair.getKey(); final String dexName = getRelativeDexName(oldFile, null); if (isDexNameMatchesClassNPattern(dexName)) { dexNameToClassNOldDexFileMap.put(dexName, oldFile); } } // If we meet a case like: // classes.dex, classes2.dex, classes4.dex, classes5.dex // Since classes3.dex is missing, according to the logic in AOSP, we should not treat // rest dexes as part of class N dexes. for (int i = 0; i < dexNameToClassNOldDexFileMap.size(); ++i) { final String expectedDexName = (i == 0 ? DexFormat.DEX_IN_JAR_NAME : "classes" + (i + 1) + ".dex"); if (dexNameToClassNOldDexFileMap.containsKey(expectedDexName)) { File oldDexFile = dexNameToClassNOldDexFileMap.get(expectedDexName); realClassNDexFiles.add(oldDexFile); } else { break; } } for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { final File oldDexFile = oldAndNewDexFilePair.getKey(); final File newDexFile = oldAndNewDexFilePair.getValue(); final String dexName = getRelativeDexName(oldDexFile, newDexFile); final RelatedInfo relatedInfo = dexNameToRelatedInfoMap.get(dexName); if (!relatedInfo.oldMd5.equals(relatedInfo.newMd5)) { logToDexMeta(newDexFile, oldDexFile, relatedInfo.dexDiffFile, relatedInfo.newOrFullPatchedMd5, relatedInfo.newOrFullPatchedMd5, relatedInfo.dexDiffMd5, relatedInfo.newOrFullPatchedCRC); } else { // For class N dexes, if new dex is the same as old dex, we should log it as 'copy directly' // in dex meta to fix problems in Art environment. if (realClassNDexFiles.contains(oldDexFile)) { // Bugfix: However, if what we would copy directly is main dex, we should do an additional diff operation // so that patch applier would help us remove all loader classes of it in runtime. if (config.mRemoveLoaderForAllDex || dexName.equals(DexFormat.DEX_IN_JAR_NAME)) { if (config.mRemoveLoaderForAllDex) { Logger.d("\nDo additional diff on every dex to remove loader classes in it, because removeLoaderForAllDex = true"); } else { Logger.d("\nDo additional diff on main dex to remove loader classes in it."); } diffDexPairAndFillRelatedInfo(oldDexFile, newDexFile, relatedInfo); logToDexMeta(newDexFile, oldDexFile, relatedInfo.dexDiffFile, relatedInfo.newOrFullPatchedMd5, relatedInfo.newOrFullPatchedMd5, relatedInfo.dexDiffMd5, relatedInfo.newOrFullPatchedCRC); } else { logToDexMeta(newDexFile, oldDexFile, null, "0", relatedInfo.oldMd5, "0", relatedInfo.newOrFullPatchedCRC); } } } } } @SuppressWarnings("NewApi") private void generatePatchedDexInfoFile() throws IOException { // Generate dex diff out and full patched dex if a pair of dex is different. for (AbstractMap.SimpleEntry oldAndNewDexFilePair : oldAndNewDexFilePairList) { File oldFile = oldAndNewDexFilePair.getKey(); File newFile = oldAndNewDexFilePair.getValue(); final String dexName = getRelativeDexName(oldFile, newFile); RelatedInfo relatedInfo = dexNameToRelatedInfoMap.get(dexName); if (!relatedInfo.oldMd5.equals(relatedInfo.newMd5)) { diffDexPairAndFillRelatedInfo(oldFile, newFile, relatedInfo); } else { // In this case newDexFile is the same as oldDexFile, but we still // need to treat it as patched dex file so that the SmallPatchGenerator // can analyze which class of this dex should be kept in small patch. relatedInfo.newOrFullPatchedFile = newFile; relatedInfo.newOrFullPatchedMd5 = relatedInfo.newMd5; relatedInfo.newOrFullPatchedCRC = FileOperation.getFileCrc32(newFile); } } } private void diffDexPairAndFillRelatedInfo(File oldDexFile, File newDexFile, RelatedInfo relatedInfo) { File tempFullPatchDexPath = new File(config.mOutFolder + File.separator + TypedValue.DEX_TEMP_PATCH_DIR); final String dexName = getRelativeDexName(oldDexFile, newDexFile); File dexDiffOut = getOutputPath(newDexFile).toFile(); ensureDirectoryExist(dexDiffOut.getParentFile()); try { DexPatchGenerator dexPatchGen = new DexPatchGenerator(oldDexFile, newDexFile); dexPatchGen.setAdditionalRemovingClassPatterns(config.mDexLoaderPattern); logWriter.writeLineToInfoFile( String.format( "Start diff between [%s] as old and [%s] as new:", getRelativeStringBy(oldDexFile, config.mTempUnzipOldDir), getRelativeStringBy(newDexFile, config.mTempUnzipNewDir) ) ); dexPatchGen.executeAndSaveTo(dexDiffOut); } catch (Exception e) { throw new TinkerPatchException(e); } if (!dexDiffOut.exists()) { throw new TinkerPatchException("can not find the diff file:" + dexDiffOut.getAbsolutePath()); } relatedInfo.dexDiffFile = dexDiffOut; relatedInfo.dexDiffMd5 = MD5.getMD5(dexDiffOut); Logger.d("\nGen %s patch file:%s, size:%d, md5:%s", dexName, relatedInfo.dexDiffFile.getAbsolutePath(), relatedInfo.dexDiffFile.length(), relatedInfo.dexDiffMd5); File tempFullPatchedDexFile = new File(tempFullPatchDexPath, dexName); if (!tempFullPatchedDexFile.exists()) { ensureDirectoryExist(tempFullPatchedDexFile.getParentFile()); } try { new DexPatchApplier(oldDexFile, dexDiffOut).executeAndSaveTo(tempFullPatchedDexFile); Logger.d( String.format("Verifying if patched new dex is logically the same as original new dex: %s ...", getRelativeStringBy(newDexFile, config.mTempUnzipNewDir)) ); Dex origNewDex = new Dex(newDexFile); Dex patchedNewDex = new Dex(tempFullPatchedDexFile); checkDexChange(origNewDex, patchedNewDex); relatedInfo.newOrFullPatchedFile = tempFullPatchedDexFile; relatedInfo.newOrFullPatchedMd5 = MD5.getMD5(tempFullPatchedDexFile); relatedInfo.newOrFullPatchedCRC = FileOperation.getFileCrc32(tempFullPatchedDexFile); } catch (Exception e) { e.printStackTrace(); throw new TinkerPatchException( "Failed to generate temporary patched dex, which makes MD5 generating procedure of new dex failed, either.", e ); } if (!tempFullPatchedDexFile.exists()) { throw new TinkerPatchException("can not find the temporary full patched dex file:" + tempFullPatchedDexFile.getAbsolutePath()); } Logger.d("\nGen %s for dalvik full dex file:%s, size:%d, md5:%s", dexName, tempFullPatchedDexFile.getAbsolutePath(), tempFullPatchedDexFile.length(), relatedInfo.newOrFullPatchedMd5); } private void addTestDex() throws IOException { //write test dex String dexMode = "jar"; if (config.mDexRaw) { dexMode = "raw"; } final InputStream is = DexDiffDecoder.class.getResourceAsStream("/" + TEST_DEX_NAME); String md5 = MD5.getMD5(is, 1024); is.close(); String meta = TEST_DEX_NAME + "," + "" + "," + md5 + "," + md5 + "," + 0 + "," + 0 + "," + 0 + "," + dexMode; File dest = new File(config.mTempResultDir + "/" + TEST_DEX_NAME); FileOperation.copyResourceUsingStream(TEST_DEX_NAME, dest); Logger.d("\nAdd test install result dex: %s, size:%d", dest.getAbsolutePath(), dest.length()); Logger.d("DexDecoder:write test dex meta file data: %s", meta); metaWriter.writeLineToInfoFile(meta); } private void checkCrossDexMovingClasses() { // Here we will check if any classes that were deleted in one dex // would be added to another dex. e.g. classA is deleted in dex0 and // added in dex1. // Since DexClassesComparator will guarantee that a class can be either 'added' // or 'deleted' between two files it compares. We can achieve our checking works // by calculating the intersection of deletedClassDescs and addedClassDescs. Set deletedClassDescs = new HashSet(deletedClassDescToDexNameMap.keySet()); Set addedClassDescs = new HashSet(addedClassDescToDexNameMap.keySet()); deletedClassDescs.retainAll(addedClassDescs); // So far deletedClassNames only contains the intersect elements between // deletedClassNames and addedClassNames. Set movedCrossFilesClassDescs = deletedClassDescs; if (!movedCrossFilesClassDescs.isEmpty()) { Logger.e("Warning:Class Moved. Some classes are just moved from one dex to another. " + "This behavior may leads to unnecessary enlargement of patch file. you should try to check them:"); for (String classDesc : movedCrossFilesClassDescs) { StringBuilder sb = new StringBuilder(); sb.append('{'); sb.append("classDesc:").append(classDesc).append(','); sb.append("from:").append(deletedClassDescToDexNameMap.get(classDesc)).append(','); sb.append("to:").append(addedClassDescToDexNameMap.get(classDesc)); sb.append('}'); Logger.e(sb.toString()); } } } /** * Before starting real diff works, we collect added class descriptor * and deleted class descriptor for further analysing in {@code checkCrossDexMovingClasses}. */ private void collectAddedOrDeletedClasses(File oldFile, File newFile) throws IOException { Dex oldDex = new Dex(oldFile); Dex newDex = new Dex(newFile); Set oldClassDescs = new HashSet<>(); for (ClassDef oldClassDef : oldDex.classDefs()) { oldClassDescs.add(oldDex.typeNames().get(oldClassDef.typeIndex)); } Set newClassDescs = new HashSet<>(); for (ClassDef newClassDef : newDex.classDefs()) { newClassDescs.add(newDex.typeNames().get(newClassDef.typeIndex)); } Set addedClassDescs = new HashSet<>(newClassDescs); addedClassDescs.removeAll(oldClassDescs); Set deletedClassDescs = new HashSet<>(oldClassDescs); deletedClassDescs.removeAll(newClassDescs); for (String addedClassDesc : addedClassDescs) { if (addedClassDescToDexNameMap.containsKey(addedClassDesc)) { throw new TinkerPatchException( String.format( "Class Duplicate. Class [%s] is added in both new dex: [%s] and [%s]. Please check your newly apk.", addedClassDesc, addedClassDescToDexNameMap.get(addedClassDesc), newFile.toString() ) ); } else { addedClassDescToDexNameMap.put(addedClassDesc, newFile.toString()); } } for (String deletedClassDesc : deletedClassDescs) { if (deletedClassDescToDexNameMap.containsKey(deletedClassDesc)) { throw new TinkerPatchException( String.format( "Class Duplicate. Class [%s] is deleted in both old dex: [%s] and [%s]. Please check your base apk.", deletedClassDesc, addedClassDescToDexNameMap.get(deletedClassDesc), oldFile.toString() ) ); } else { deletedClassDescToDexNameMap.put(deletedClassDesc, newFile.toString()); } } } private boolean isDexNameMatchesClassNPattern(String dexName) { return (dexName.matches("^classes[0-9]*\\.dex$")); } private void copyNewDexAndLogToDexMeta(File newFile, String newMd5, File output) throws IOException { FileOperation.copyFileUsingStream(newFile, output); final long newFileCrc = FileOperation.getFileCrc32(newFile); logToDexMeta(newFile, null, null, newMd5, newMd5, "0", newFileCrc); } private void checkDexChange(Dex originDex, Dex newDex) { DexClassesComparator classesCmptor = new DexClassesComparator("*"); classesCmptor.setIgnoredRemovedClassDescPattern(config.mDexLoaderPattern); classesCmptor.startCheck(originDex, newDex); List addedClassInfos = classesCmptor.getAddedClassInfos(); boolean isNoClassesAdded = addedClassInfos.isEmpty(); Map changedClassDescToClassInfosMap; boolean isNoClassesChanged; if (isNoClassesAdded) { changedClassDescToClassInfosMap = classesCmptor.getChangedClassDescToInfosMap(); isNoClassesChanged = changedClassDescToClassInfosMap.isEmpty(); } else { throw new TinkerPatchException( "some classes was unexpectedly added in patched new dex, check if there's any bugs in " + "patch algorithm. Related classes: " + Utils.collectionToString(addedClassInfos) ); } if (isNoClassesChanged) { List deletedClassInfos = classesCmptor.getDeletedClassInfos(); if (!deletedClassInfos.isEmpty()) { throw new TinkerPatchException( "some classes that are not matched to loader class pattern " + "was unexpectedly deleted in patched new dex, check if there's any bugs in " + "patch algorithm. Related classes: " + Utils.collectionToString(deletedClassInfos) ); } } else { throw new TinkerPatchException( "some classes was unexpectedly changed in patched new dex, check if there's any bugs in " + "patch algorithm. Related classes: " + Utils.collectionToString(changedClassDescToClassInfosMap.keySet()) ); } } /** * Construct dex meta-info and write it to meta file and log. * * @param newFile * New dex file. * @param oldFile * Old dex file. * @param dexDiffFile * Dex diff file. (Generated by DexPatchGenerator.) * @param destMd5InDvm * Md5 of output dex in dvm environment, could be full patched dex md5 or new dex. * @param destMd5InArt * Md5 of output dex in dvm environment, could be small patched dex md5 or new dex. * @param dexDiffMd5 * Md5 of dex patch info file. * @param newOrFullPatchedCrc * CRC32 of new dex or full patched dex. * * @throws IOException */ protected void logToDexMeta(File newFile, File oldFile, File dexDiffFile, String destMd5InDvm, String destMd5InArt, String dexDiffMd5, long newOrFullPatchedCrc) { if (metaWriter == null && logWriter == null) { return; } String parentRelative = getParentRelativePathStringToNewFile(newFile); String relative = getRelativePathStringToNewFile(newFile); if (metaWriter != null) { String fileName = newFile.getName(); String dexMode = "jar"; if (config.mDexRaw) { dexMode = "raw"; } //new file String oldCrc; if (oldFile == null) { oldCrc = "0"; Logger.d("DexDecoder:add newly dex file: %s", parentRelative); } else { oldCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, relative); if (oldCrc == null || oldCrc.equals("0")) { throw new TinkerPatchException( String.format("can't find zipEntry %s from old apk file %s", relative, config.mOldApkFile.getPath()) ); } } String meta = fileName + "," + parentRelative + "," + destMd5InDvm + "," + destMd5InArt + "," + dexDiffMd5 + "," + oldCrc + "," + newOrFullPatchedCrc + "," + dexMode; Logger.d("DexDecoder:write meta file data: %s", meta); metaWriter.writeLineToInfoFile(meta); } if (logWriter != null) { String log = relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile) + ", diffSize=" + FileOperation.getFileSizes(dexDiffFile); logWriter.writeLineToInfoFile(log); } } @Override public void clean() { metaWriter.close(); logWriter.close(); } private String getRawOrWrappedDexMD5(File dexOrJarFile) { final String name = dexOrJarFile.getName(); if (name.endsWith(".dex")) { return MD5.getMD5(dexOrJarFile); } else { JarFile dexJar = null; try { dexJar = new JarFile(dexOrJarFile); ZipEntry classesDex = dexJar.getEntry(DexFormat.DEX_IN_JAR_NAME); // no code if (classesDex == null) { throw new TinkerPatchException( String.format("Jar file %s do not contain 'classes.dex', it is not a correct dex jar file!", dexOrJarFile.getAbsolutePath()) ); } return MD5.getMD5(dexJar.getInputStream(classesDex), 1024 * 100); } catch (IOException e) { throw new TinkerPatchException( String.format("File %s is not end with '.dex', but it is not a correct dex jar file !", dexOrJarFile.getAbsolutePath()), e ); } finally { if (dexJar != null) { try { dexJar.close(); } catch (Exception e) { // Ignored. } } } } } private String getRelativeStringBy(File file, File reference) { File actualReference = reference.getParentFile(); if (actualReference == null) { actualReference = reference; } return actualReference.toPath().relativize(file.toPath()).toString().replace("\\", "/"); } private void ensureDirectoryExist(File dir) { if (!dir.exists()) { if (!dir.mkdirs()) { throw new TinkerPatchException("failed to create directory: " + dir); } } } private final class RelatedInfo { File newOrFullPatchedFile = null; /** * This field could be null if old dex and new dex * are the same. */ File dexDiffFile = null; String oldMd5 = "0"; String newMd5 = "0"; String dexDiffMd5 = "0"; /** * This field could be one of the following value: * fullPatchedDex md5, if old dex and new dex are different; * newDex md5, if new dex is marked to be copied directly. */ String newOrFullPatchedMd5 = "0"; /** * This field is used to generate class-N dex jar on app runtime. * It could be one of the following value: * CRC32 of full patched dex, if old dex and new dex are different; * CRC32 of new dex, if new dex is marked to be copied directly. */ long newOrFullPatchedCRC = 0; } private final class DexPatcherLoggerBridge implements IDexPatcherLogger { private final InfoWriter logWriter; DexPatcherLoggerBridge(InfoWriter logWritter) { this.logWriter = logWritter; } @Override public void v(String msg) { this.logWriter.writeLineToInfoFile(msg); } @Override public void d(String msg) { this.logWriter.writeLineToInfoFile(msg); } @Override public void i(String msg) { this.logWriter.writeLineToInfoFile(msg); } @Override public void w(String msg) { this.logWriter.writeLineToInfoFile(msg); } @Override public void e(String msg) { this.logWriter.writeLineToInfoFile(msg); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ManifestDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.apkparser.AndroidParser; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.build.util.Utils; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.XMLWriter; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import tinker.net.dongliu.apk.parser.bean.ApkMeta; import tinker.net.dongliu.apk.parser.bean.GlEsVersion; import tinker.net.dongliu.apk.parser.bean.Permission; import tinker.net.dongliu.apk.parser.bean.UseFeature; /** * Created by zhangshaowen on 16/4/6. */ public class ManifestDecoder extends BaseDecoder { private static final String XML_NODENAME_APPLICATION = "application"; private static final String XML_NODENAME_USES_SDK = "uses-sdk"; private static final String XML_NODEATTR_MIN_SDK_VERSION = "minSdkVersion"; private static final String XML_NODEATTR_TARGET_SDK_VERSION = "targetSdkVersion"; private static final String XML_NODEATTR_PACKAGE = "package"; private static final String XML_NODENAME_ACTIVITY = "activity"; private static final String XML_NODENAME_SERVICE = "service"; private static final String XML_NODENAME_RECEIVER = "receiver"; private static final String XML_NODENAME_PROVIDER = "provider"; private static final String XML_NODEATTR_NAME = "name"; private static final String XML_NODEATTR_EXPORTED = "exported"; private static final String XML_NODEATTR_PROCESS = "process"; private static final String XML_NODENAME_INTENTFILTER = "intent-filter"; public ManifestDecoder(Configuration config) throws IOException { super(config); } @Override public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { try { AndroidParser oldAndroidManifest = AndroidParser.getAndroidManifest(oldFile); AndroidParser newAndroidManifest = AndroidParser.getAndroidManifest(newFile); //check minSdkVersion int minSdkVersion = Integer.parseInt(oldAndroidManifest.apkMeta.getMinSdkVersion()); if (minSdkVersion < TypedValue.ANDROID_40_API_LEVEL) { if (config.mDexRaw) { final StringBuilder sb = new StringBuilder(); sb.append("your old apk's minSdkVersion ") .append(minSdkVersion) .append(" is below 14, you should set the dexMode to 'jar', ") .append("otherwise, it will crash at some time"); announceWarningOrException(sb.toString()); } } final String oldXml = oldAndroidManifest.xml.trim(); final String newXml = newAndroidManifest.xml.trim(); final boolean isManifestChanged = !oldXml.equals(newXml); if (!isManifestChanged) { Logger.d("\nManifest has no changes, skip rest decode works."); return false; } ensureApkMetaUnchanged(oldAndroidManifest.apkMeta, newAndroidManifest.apkMeta); // check whether there is any new Android Component and get their names. // so far only Activity increment can pass checking. final Set incActivities = getIncrementActivities(oldAndroidManifest.activities, newAndroidManifest.activities); final Set incServices = getIncrementServices(oldAndroidManifest.services, newAndroidManifest.services); final Set incReceivers = getIncrementReceivers(oldAndroidManifest.receivers, newAndroidManifest.receivers); final Set incProviders = getIncrementProviders(oldAndroidManifest.providers, newAndroidManifest.providers); final boolean hasIncComponent = (!incActivities.isEmpty() || !incServices.isEmpty() || !incProviders.isEmpty() || !incReceivers.isEmpty()); if (!config.mSupportHotplugComponent && hasIncComponent) { announceWarningOrException("manifest was changed, while hot plug component support mode is disabled. " + "Such changes will not take effect, related components: \n" + " activity: " + incActivities + "\n" + " service: " + incServices + "\n" + " receiver: " + incReceivers + "\n" + " provider: " + incProviders + "\n" ); } // generate increment manifest. if (hasIncComponent) { final Document newXmlDoc = DocumentHelper.parseText(newAndroidManifest.xml); final Document incXmlDoc = DocumentHelper.createDocument(); final Element newRootNode = newXmlDoc.getRootElement(); final String packageName = newRootNode.attributeValue(XML_NODEATTR_PACKAGE); if (Utils.isNullOrNil(packageName)) { throw new TinkerPatchException("Unable to find package name from manifest: " + newFile.getAbsolutePath()); } final Element newAppNode = newRootNode.element(XML_NODENAME_APPLICATION); final Element incAppNode = incXmlDoc.addElement(newAppNode.getQName()); copyAttributes(newAppNode, incAppNode); if (!incActivities.isEmpty()) { final List newActivityNodes = newAppNode.elements(XML_NODENAME_ACTIVITY); final List incActivityNodes = getIncrementActivityNodes(packageName, newActivityNodes, incActivities); for (Element node : incActivityNodes) { incAppNode.add(node.detach()); } } if (!incServices.isEmpty()) { final List newServiceNodes = newAppNode.elements(XML_NODENAME_SERVICE); final List incServiceNodes = getIncrementServiceNodes(packageName, newServiceNodes, incServices); for (Element node : incServiceNodes) { incAppNode.add(node.detach()); } } if (!incReceivers.isEmpty()) { final List newReceiverNodes = newAppNode.elements(XML_NODENAME_RECEIVER); final List incReceiverNodes = getIncrementReceiverNodes(packageName, newReceiverNodes, incReceivers); for (Element node : incReceiverNodes) { incAppNode.add(node.detach()); } } if (!incProviders.isEmpty()) { final List newProviderNodes = newAppNode.elements(XML_NODENAME_PROVIDER); final List incProviderNodes = getIncrementProviderNodes(packageName, newProviderNodes, incProviders); for (Element node : incProviderNodes) { incAppNode.add(node.detach()); } } final File incXmlOutput = new File(config.mTempResultDir, TypedValue.INCCOMPONENT_META_FILE); if (!incXmlOutput.exists()) { incXmlOutput.getParentFile().mkdirs(); } OutputStream os = null; try { os = new BufferedOutputStream(new FileOutputStream(incXmlOutput)); final XMLWriter docWriter = new XMLWriter(os); docWriter.write(incXmlDoc); docWriter.close(); } finally { Utils.closeQuietly(os); } } if (isManifestChanged && !hasIncComponent) { Logger.d("\nManifest was changed, while there's no any new components added." + " Make sure if such changes were all you expected.\n"); } } catch (ParseException e) { e.printStackTrace(); throw new TinkerPatchException("Parse android manifest error!"); } catch (DocumentException e) { e.printStackTrace(); throw new TinkerPatchException("Parse android manifest by dom4j error!"); } catch (IOException e) { e.printStackTrace(); throw new TinkerPatchException("Failed to generate increment manifest.", e); } return false; } private void ensureApkMetaUnchanged(ApkMeta oldMeta, ApkMeta newMeta) { if (oldMeta == null && newMeta == null) { // Impossible situation, for edge case protection only. return; } if (oldMeta != null && newMeta != null) { if (!nullSafeEquals(oldMeta.getPackageName(), newMeta.getPackageName(), null)) { announceWarningOrException("Package name changed, old: " + oldMeta.getPackageName() + ", new: " + newMeta.getPackageName()); } if (!nullSafeEquals(oldMeta.getLabel(), newMeta.getLabel(), null)) { announceWarningOrException("App label changed, old: " + oldMeta.getLabel() + ", new: " + newMeta.getLabel()); } if (!nullSafeEquals(oldMeta.getIcon(), newMeta.getIcon(), null)) { announceWarningOrException("App icon res ref changed, old: " + oldMeta.getIcon() + ", new: " + newMeta.getIcon()); } if (!nullSafeEquals(oldMeta.getVersionName(), newMeta.getVersionName(), null)) { Logger.e("Note: Version name changed, old: " + oldMeta.getVersionName() + ", new: " + newMeta.getVersionName()); } final Long oldVersionCode = oldMeta.getVersionCode(); final Long newVersionCode = newMeta.getVersionCode(); if (oldVersionCode != null && newVersionCode != null) { if (newVersionCode < oldVersionCode) { announceWarningOrException("Version code downgrade, old: " + oldVersionCode + ", new: " + newVersionCode); } } else if (!(oldVersionCode == null && newVersionCode == null)) { announceWarningOrException("Version code of old or new apk is missing, old: " + oldVersionCode + ", new: " + newVersionCode); } if (!nullSafeEquals(oldMeta.getInstallLocation(), newMeta.getInstallLocation(), null)) { announceWarningOrException("Install location changed, old: " + oldMeta.getInstallLocation() + ", new: " + newMeta.getInstallLocation()); } if (!nullSafeEquals(oldMeta.getMinSdkVersion(), newMeta.getMinSdkVersion(), null)) { announceWarningOrException("MinSdkVersion changed, old: " + oldMeta.getMinSdkVersion() + ", new: " + newMeta.getMinSdkVersion()); } if (!nullSafeEquals(oldMeta.getTargetSdkVersion(), newMeta.getTargetSdkVersion(), null)) { announceWarningOrException("TargetSdkVersion changed, old: " + oldMeta.getTargetSdkVersion() + ", new: " + newMeta.getTargetSdkVersion()); } if (!nullSafeEquals(oldMeta.getMaxSdkVersion(), newMeta.getMaxSdkVersion(), null)) { announceWarningOrException("MaxSdkVersion changed, old: " + oldMeta.getMaxSdkVersion() + ", new: " + newMeta.getMaxSdkVersion()); } if (!nullSafeEquals(oldMeta.getGlEsVersion(), newMeta.getGlEsVersion(), GLES_VERSION_EQUALS)) { announceWarningOrException("GLEsVersion changed, old: " + GLES_VERSION_DESCRIBER.describe(oldMeta.getGlEsVersion()) + ", new: " + GLES_VERSION_DESCRIBER.describe(newMeta.getGlEsVersion())); } if (!nullSafeEquals(oldMeta.isAnyDensity(), newMeta.isAnyDensity(), null)) { announceWarningOrException("Value of isAnyDensity changed, old: " + oldMeta.isAnyDensity() + ", new: " + newMeta.isAnyDensity()); } if (!nullSafeEquals(oldMeta.isSmallScreens(), newMeta.isSmallScreens(), null)) { announceWarningOrException("Value of isSmallScreens changed, old: " + oldMeta.isSmallScreens() + ", new: " + newMeta.isSmallScreens()); } if (!nullSafeEquals(oldMeta.isNormalScreens(), newMeta.isNormalScreens(), null)) { announceWarningOrException("Value of isNormalScreens changed, old: " + oldMeta.isNormalScreens() + ", new: " + newMeta.isNormalScreens()); } if (!nullSafeEquals(oldMeta.isLargeScreens(), newMeta.isLargeScreens(), null)) { announceWarningOrException("Value of isLargeScreens changed, old: " + oldMeta.isLargeScreens() + ", new: " + newMeta.isLargeScreens()); } if (!nullSafeEqualsIgnoreOrder(oldMeta.getUsesPermissions(), newMeta.getUsesPermissions(), null)) { announceWarningOrException("Uses permissions changed, related uses-permissions: " + describeChanges(oldMeta.getUsesPermissions(), newMeta.getUsesPermissions())); } if (!nullSafeEqualsIgnoreOrder(oldMeta.getUsesFeatures(), newMeta.getUsesFeatures(), USE_FEATURE_DESCRIBER)) { announceWarningOrException("Uses features changed, related uses-features: " + describeChanges(oldMeta.getUsesFeatures(), newMeta.getUsesFeatures(), USE_FEATURE_DESCRIBER)); } if (!nullSafeEqualsIgnoreOrder(oldMeta.getPermissions(), newMeta.getPermissions(), PERMISSION_DESCRIBER)) { announceWarningOrException("Uses features changed, related permissions: " + describeChanges(oldMeta.getPermissions(), newMeta.getPermissions(), PERMISSION_DESCRIBER)); } } else { announceWarningOrException("One of apk meta is null, are we processing invalid manifest ?"); } } private interface EqualsChecker { boolean isEquals(T lhs, T rhs); } private static final EqualsChecker GLES_VERSION_EQUALS = new EqualsChecker() { @Override public boolean isEquals(GlEsVersion lhs, GlEsVersion rhs) { if (lhs.getMajor() != rhs.getMajor()) { return false; } if (lhs.getMinor() != rhs.getMinor()) { return false; } return lhs.isRequired() == rhs.isRequired(); } }; private static boolean nullSafeEquals(T lhs, T rhs, EqualsChecker equalsChecker) { if (lhs == null && rhs == null) { return true; } if (lhs != null && rhs != null) { return (equalsChecker != null ? equalsChecker.isEquals(lhs, rhs) : lhs.equals(rhs)); } return false; } private static boolean nullSafeEqualsIgnoreOrder(List lhs, List rhs, ObjectDescriber describer) { if (lhs == null && rhs == null) { return true; } if (lhs != null && rhs != null) { final Set lhsDescs = new HashSet<>(); int lhsNotNullElemCount = 0; int rhsNotNullElemCount = 0; for (int i = 0; i < lhs.size(); ++i) { final T lhsElem = lhs.get(i); if (lhsElem == null) { continue; } lhsDescs.add(describer != null ? describer.describe(lhsElem) : lhsElem.toString()); ++lhsNotNullElemCount; } boolean hasAddedElemDesc = false; for (int i = 0; i < rhs.size(); ++i) { final T rhsElem = rhs.get(i); if (rhsElem == null) { continue; } final String rhsElemDesc = describer != null ? describer.describe(rhsElem) : rhsElem.toString(); if (!lhsDescs.remove(rhsElemDesc)) { hasAddedElemDesc = true; break; } ++rhsNotNullElemCount; } if (hasAddedElemDesc) { return false; } if (lhsDescs.size() > 0) { // Has removed items. return false; } return lhsNotNullElemCount == rhsNotNullElemCount; } return false; } private interface ObjectDescriber { String describe(T obj); } private static final ObjectDescriber GLES_VERSION_DESCRIBER = new ObjectDescriber() { @Override public String describe(GlEsVersion obj) { final StringBuilder sb = new StringBuilder(); sb.append("{") .append("majar:").append(obj.getMajor()) .append("minor:").append(obj.getMinor()) .append(",required:").append(obj.isRequired()) .append("}"); return sb.toString(); } }; private static final ObjectDescriber USE_FEATURE_DESCRIBER = new ObjectDescriber() { @Override public String describe(UseFeature obj) { final StringBuilder sb = new StringBuilder(); sb.append("{") .append("name:").append(obj.getName()) .append(",required:").append(obj.isRequired()) .append("}"); return sb.toString(); } }; private static final ObjectDescriber PERMISSION_DESCRIBER = new ObjectDescriber() { @Override public String describe(Permission obj) { final StringBuilder sb = new StringBuilder(); sb.append("{") .append("name:").append(obj.getName()) .append(",label:").append(obj.getLabel()) .append(",icon:").append(obj.getIcon()) .append(",description:").append(obj.getDescription()) .append(",group:").append(obj.getGroup()) .append(",protectionLevel:").append(obj.getProtectionLevel()) .append("}"); return sb.toString(); } }; private static String describeChanges(Collection oldObjs, Collection newObjs) { return describeChanges(oldObjs, newObjs, null); } private static String describeChanges(Collection oldObjs, Collection newObjs, ObjectDescriber describer) { final Set oldDescs = new HashSet<>(); final List addedDescs = new ArrayList<>(); for (T oldObj : oldObjs) { oldDescs.add(describer != null ? describer.describe(oldObj) : oldObj.toString()); } for (T newObj : newObjs) { final String newDesc = describer != null ? describer.describe(newObj) : newObj.toString(); if (!oldDescs.remove(newDesc)) { addedDescs.add(newDesc); } } final List removedDescs = new ArrayList<>(oldDescs); final StringBuilder sb = new StringBuilder(); sb.append("{added:").append(addedDescs) .append(",removed:").append(removedDescs) .append("}"); return sb.toString(); } private Set getIncrementActivities(Collection oldActivities, Collection newActivities) { final Set incNames = new HashSet<>(newActivities); incNames.removeAll(oldActivities); return incNames; } private Set getIncrementServices(Collection oldServices, Collection newServices) { final Set incNames = new HashSet<>(newServices); incNames.removeAll(oldServices); if (!incNames.isEmpty()) { announceWarningOrException("found added services: " + incNames.toString() + "\n currently tinker does not support increase new services, " + "such these changes would not take effect."); } return incNames; } private Set getIncrementReceivers(Collection oldReceivers, Collection newReceivers) { final Set incNames = new HashSet<>(newReceivers); incNames.removeAll(oldReceivers); if (!incNames.isEmpty()) { announceWarningOrException("found added receivers: " + incNames.toString() + "\n currently tinker does not support increase new receivers, " + "such these changes would not take effect."); } return incNames; } private Set getIncrementProviders(Collection oldProviders, Collection newProviders) { final Set incNames = new HashSet<>(newProviders); incNames.removeAll(oldProviders); if (!incNames.isEmpty()) { announceWarningOrException("found added providers: " + incNames.toString() + "\n currently tinker does not support increase new providers, " + "such these changes would not take effect."); } return incNames; } private List getIncrementActivityNodes(String packageName, List newActivityNodes, Collection incActivities) { final List result = new ArrayList<>(); for (Element newActivityNode : newActivityNodes) { String activityClazzName = newActivityNode.attributeValue(XML_NODEATTR_NAME); if (activityClazzName.charAt(0) == '.') { activityClazzName = packageName + activityClazzName; } if (!incActivities.contains(activityClazzName)) { continue; } final String exportedVal = newActivityNode.attributeValue(XML_NODEATTR_EXPORTED, Utils.isNullOrNil(newActivityNode.elements(XML_NODENAME_INTENTFILTER)) ? "false" : "true"); if ("true".equalsIgnoreCase(exportedVal)) { announceWarningOrException( String.format("found a new exported activity %s" + ", tinker does not support increase exported activity.", activityClazzName) ); } final String processVal = newActivityNode.attributeValue(XML_NODEATTR_PROCESS); if (processVal != null && processVal.charAt(0) == ':') { announceWarningOrException( String.format("found a new activity %s which would be run in standalone process" + ", tinker does not support increase such kind of activities.", activityClazzName) ); } Logger.d("Found increment activity: " + activityClazzName); result.add(newActivityNode); } return result; } private List getIncrementServiceNodes(String packageName, List newServiceNodes, Collection incServices) { announceWarningOrException("currently tinker does not support increase new services."); return Collections.emptyList(); } private List getIncrementReceiverNodes(String packageName, List newReceiverNodes, Collection incReceivers) { announceWarningOrException("currently tinker does not support increase new receivers."); return Collections.emptyList(); } private List getIncrementProviderNodes(String packageName, List newProviderNodes, Collection incProviders) { announceWarningOrException("currently tinker does not support increase new providers."); return Collections.emptyList(); } private void copyAttributes(Element srcNode, Element destNode) { for (Object attrObj : srcNode.attributes()) { final Attribute attr = (Attribute) attrObj; destNode.addAttribute(attr.getQName(), attr.getValue()); } } private void announceWarningOrException(String message) { if (config.mIgnoreWarning) { final String msg = "Warning:ignoreWarning is true, but " + message; Logger.e(msg); } else { final String msg = "Warning:ignoreWarning is false, " + message; Logger.e(msg); throw new TinkerPatchException(msg); } } @Override public void onAllPatchesStart() throws IOException, TinkerPatchException { } @Override public void onAllPatchesEnd() throws IOException, TinkerPatchException { } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/ResDiffDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.apkparser.AndroidParser; import com.tencent.tinker.build.info.InfoWriter; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.DiffFactory; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.CustomDiff; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.MD5; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.build.util.Utils; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import tinker.net.dongliu.apk.parser.ApkParser; import tinker.net.dongliu.apk.parser.struct.ResourceValue; import tinker.net.dongliu.apk.parser.struct.resource.ResourceEntry; import tinker.net.dongliu.apk.parser.struct.resource.ResourcePackage; import tinker.net.dongliu.apk.parser.struct.resource.Type; /** * Created by zhangshaowen on 16/8/8. */ public class ResDiffDecoder extends BaseDecoder { private static final String TEST_RESOURCE_NAME = "only_use_to_test_tinker_resource.txt"; private static final String TEST_RESOURCE_ASSETS_PATH = "assets/" + TEST_RESOURCE_NAME; private static final String TEMP_RES_ZIP = "temp_res.zip"; private final InfoWriter logWriter; private final InfoWriter metaWriter; private ArrayList addedSet; private ArrayList modifiedSet; private ArrayList storedSet; private ArrayList largeModifiedSet; private HashMap largeModifiedMap; private ArrayList deletedSet; private ApkParser newApkParser; private Set newApkAnimResNames; public ResDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { super(config); if (metaPath != null) { metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); } else { metaWriter = null; } if (logPath != null) { logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); } else { logWriter = null; } addedSet = new ArrayList<>(); modifiedSet = new ArrayList<>(); largeModifiedSet = new ArrayList<>(); largeModifiedMap = new HashMap<>(); deletedSet = new ArrayList<>(); storedSet = new ArrayList<>(); newApkParser = new ApkParser(config.mNewApkFile); newApkAnimResNames = new HashSet<>(); } @Override public void clean() { metaWriter.close(); logWriter.close(); try { newApkParser.close(); } catch (Throwable ignored) { // Ignored. } } /** * last modify or store files * * @param file * @return */ private boolean checkLargeModFile(File file) { long length = file.length(); if (length > config.mLargeModSize * TypedValue.K_BYTES) { return true; } return false; } @Override public void onAllPatchesStart() throws IOException, TinkerPatchException { newApkParser.parseResourceTable(); final Map newApkResPkgNameMap = newApkParser.getResourceTable().getPackageNameMap(); do { if (newApkResPkgNameMap == null) { break; } final ResourcePackage newApkResPackage = newApkResPkgNameMap.get(newApkParser.getApkMeta().getPackageName()); if (newApkResPackage == null) { break; } final Map> newApkResTypesNameMap = newApkResPackage.getTypesNameMap(); if (newApkResTypesNameMap == null) { break; } final List newApkAnimResTypes = newApkResTypesNameMap.get("anim"); if (newApkAnimResTypes == null) { break; } for (Type animType : newApkAnimResTypes) { for (ResourceEntry value : animType.getResourceEntryNameHashMap().values()) { if (value == null) { continue; } final ResourceValue resValue = value.getValue(); if (resValue == null) { continue; } newApkAnimResNames.add(resValue.toStringValue()); } } } while (false); } @Override public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { String name = getRelativePathStringToNewFile(newFile); //actually, it won't go below if (newFile == null || !newFile.exists()) { String relativeStringByOldDir = getRelativePathStringToOldFile(oldFile); if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, relativeStringByOldDir)) { Logger.e("found delete resource: " + relativeStringByOldDir + " ,but it match ignore change pattern, just ignore!"); return false; } deletedSet.add(relativeStringByOldDir); writeResLog(newFile, oldFile, TypedValue.DEL); return true; } File outputFile = getOutputPath(newFile).toFile(); if (oldFile == null || !oldFile.exists()) { if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { Logger.e("found add resource: " + name + " ,but it match ignore change pattern, just ignore!"); return false; } FileOperation.copyFileUsingStream(newFile, outputFile); addedSet.add(name); writeResLog(newFile, oldFile, TypedValue.ADD); return true; } //both file length is 0 if (oldFile.length() == 0 && newFile.length() == 0) { return false; } //new add file String newMd5 = MD5.getMD5(newFile); String oldMd5 = MD5.getMD5(oldFile); //oldFile or newFile may be 0b length if (oldMd5 != null && oldMd5.equals(newMd5)) { return false; } if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { Logger.d("found modify resource: " + name + ", but it match ignore change pattern, just ignore!"); return false; } if (name.equals(TypedValue.RES_MANIFEST)) { Logger.d("found modify resource: " + name + ", but it is AndroidManifest.xml, just ignore!"); return false; } // Even if the new resources.arsc is logically the same as the old one, we still // need to generate diff for it since resources reordering can cause locating issue // too. // if (name.equals(TypedValue.RES_ARSC)) { // if (AndroidParser.resourceTableLogicalChange(config)) { // Logger.d("found modify resource: " + name + ", but it is logically the same as original new resources.arsc, just ignore!"); // return false; // } // } dealWithModifyFile(name, newMd5, oldFile, newFile, outputFile); return true; } private boolean dealWithModifyFile(String name, String newMd5, File oldFile, File newFile, File outputFile) throws IOException { if (checkLargeModFile(newFile)) { if (!outputFile.getParentFile().exists()) { outputFile.getParentFile().mkdirs(); } DiffFactory.diffFile(config, oldFile, newFile, outputFile); //treat it as normal modify if (Utils.checkBsDiffFileSize(outputFile, newFile)) { LargeModeInfo largeModeInfo = new LargeModeInfo(); largeModeInfo.path = newFile; largeModeInfo.crc = FileOperation.getFileCrc32(newFile); largeModeInfo.md5 = newMd5; largeModifiedSet.add(name); largeModifiedMap.put(name, largeModeInfo); writeResLog(newFile, oldFile, TypedValue.LARGE_MOD); return true; } } modifiedSet.add(name); FileOperation.copyFileUsingStream(newFile, outputFile); writeResLog(newFile, oldFile, TypedValue.MOD); return false; } private void writeResLog(File newFile, File oldFile, int mode) throws IOException { if (logWriter != null) { String log = ""; String relative; switch (mode) { case TypedValue.ADD: relative = getRelativePathStringToNewFile(newFile); Logger.d("Found add resource: " + relative); log = "add resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile); break; case TypedValue.MOD: relative = getRelativePathStringToNewFile(newFile); Logger.d("Found modify resource: " + relative); log = "modify resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile); break; case TypedValue.DEL: relative = getRelativePathStringToOldFile(oldFile); Logger.d("Found deleted resource: " + relative); log = "deleted resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile); break; case TypedValue.LARGE_MOD: relative = getRelativePathStringToNewFile(newFile); Logger.d("Found large modify resource: " + relative + " size:" + newFile.length()); log = "large modify resource: " + relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile); break; default: break; } logWriter.writeLineToInfoFile(log); } } private void addAssetsFileForTestResource() throws IOException { File dest = new File(config.mTempResultDir + "/" + TEST_RESOURCE_ASSETS_PATH); FileOperation.copyResourceUsingStream(TEST_RESOURCE_NAME, dest); addedSet.add(TEST_RESOURCE_ASSETS_PATH); Logger.d("Add Test resource file: " + TEST_RESOURCE_ASSETS_PATH); String log = "add test resource: " + TEST_RESOURCE_ASSETS_PATH + ", oldSize=" + 0 + ", newSize=" + FileOperation.getFileSizes(dest); logWriter.writeLineToInfoFile(log); } @Override public void onAllPatchesEnd() throws IOException, TinkerPatchException { //only there is only deleted set, we just ignore if (addedSet.isEmpty() && modifiedSet.isEmpty() && largeModifiedSet.isEmpty()) { return; } if (!config.mResRawPattern.contains(TypedValue.RES_ARSC)) { throw new TinkerPatchException("resource must contain resources.arsc pattern"); } if (!config.mResRawPattern.contains(TypedValue.RES_MANIFEST)) { throw new TinkerPatchException("resource must contain AndroidManifest.xml pattern"); } //check gradle build if (config.mUsingGradle) { final boolean ignoreWarning = config.mIgnoreWarning; final boolean resourceArscChanged = modifiedSet.contains(TypedValue.RES_ARSC) || largeModifiedSet.contains(TypedValue.RES_ARSC); if (resourceArscChanged && !config.mUseApplyResource) { if (ignoreWarning) { //ignoreWarning, just log Logger.e("Warning:ignoreWarning is true, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times"); } else { Logger.e("Warning:ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times"); throw new TinkerPatchException( String.format("ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times") ); } } /*else if (config.mUseApplyResource) { int totalChangeSize = addedSet.size() + modifiedSet.size() + largeModifiedSet.size(); if (totalChangeSize == 1 && resourceArscChanged) { Logger.e("Warning: we are using applyResourceMapping mode to build the new apk, but there is only resources.arsc changed, you should ensure there is actually resource changed!"); } }*/ } //add delete set deletedSet.addAll(getDeletedResource(config.mTempUnzipOldDir, config.mTempUnzipNewDir)); //we can't modify AndroidManifest file addedSet.remove(TypedValue.RES_MANIFEST); deletedSet.remove(TypedValue.RES_MANIFEST); modifiedSet.remove(TypedValue.RES_MANIFEST); largeModifiedSet.remove(TypedValue.RES_MANIFEST); //remove add, delete or modified if they are in ignore change pattern also removeIgnoreChangeFile(modifiedSet); removeIgnoreChangeFile(deletedSet); removeIgnoreChangeFile(addedSet); removeIgnoreChangeFile(largeModifiedSet); // after ignore-changes resource files are being removed, we now check if there's any anim // resources in added and modified files. checkIfSpecificResWasAnimRes(addedSet); checkIfSpecificResWasAnimRes(modifiedSet); checkIfSpecificResWasAnimRes(largeModifiedSet); // last add test res in assets for user cannot ignore it; addAssetsFileForTestResource(); File tempResZip = new File(config.mOutFolder + File.separator + TEMP_RES_ZIP); final File tempResFiles = config.mTempResultDir; //gen zip resources_out.zip FileOperation.zipInputDir(tempResFiles, tempResZip, null); File extractToZip = new File(config.mOutFolder + File.separator + TypedValue.RES_OUT); String resZipMd5 = Utils.genResOutputFile(extractToZip, tempResZip, config, addedSet, modifiedSet, deletedSet, largeModifiedSet, largeModifiedMap); Logger.e("Final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5); logWriter.writeLineToInfoFile( String.format("Final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5) ); //delete temp file FileOperation.deleteFile(tempResZip); //first, write resource meta first //use resources.arsc's base crc to identify base.apk String arscBaseCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, TypedValue.RES_ARSC); String arscMd5 = FileOperation.getZipEntryMd5(extractToZip, TypedValue.RES_ARSC); if (arscBaseCrc == null || arscMd5 == null) { throw new TinkerPatchException("can't find resources.arsc's base crc or md5"); } String resourceMeta = Utils.getResourceMeta(arscBaseCrc, arscMd5); writeMetaFile(resourceMeta); //pattern String patternMeta = TypedValue.PATTERN_TITLE; HashSet patterns = new HashSet<>(config.mResRawPattern); //we will process them separate patterns.remove(TypedValue.RES_MANIFEST); writeMetaFile(patternMeta + patterns.size()); //write pattern for (String item : patterns) { writeMetaFile(item); } //add store files getCompressMethodFromApk(); //write meta file, write large modify first writeMetaFile(largeModifiedSet, TypedValue.LARGE_MOD); writeMetaFile(modifiedSet, TypedValue.MOD); writeMetaFile(addedSet, TypedValue.ADD); writeMetaFile(deletedSet, TypedValue.DEL); writeMetaFile(storedSet, TypedValue.STORED); } private void checkIfSpecificResWasAnimRes(Collection specificFileNames) { final Set changedAnimResNames = new HashSet<>(); for (String resFileName : specificFileNames) { if (newApkAnimResNames.contains(resFileName)) { if (Utils.isStringMatchesPatterns(resFileName, config.mResIgnoreChangeWarningPattern)) { Logger.d("\nAnimation resource: " + resFileName + " was changed, but it's filtered by ignoreChangeWarning pattern, just ignore.\n"); } else { changedAnimResNames.add(resFileName); } } } if (!changedAnimResNames.isEmpty()) { if (config.mIgnoreWarning) { //ignoreWarning, just log Logger.e("Warning:ignoreWarning is true, but we found animation resource is changed. " + "Please check if any one was used in 'overridePendingTransition' which may leads to crash. " + "If all of them were not used in that method, just add them into 'res { ignoreChangeWarning }' option.\n" + "related res: " + changedAnimResNames + "\n"); } else { Logger.e("Warning:ignoreWarning is false, but we found animation resource is changed. " + "Please check if any one was used in 'overridePendingTransition' which may leads to crash. " + "If all of them were not used in that method, just add them into 'res { ignoreChangeWarning }' option.\n" + "related res: " + changedAnimResNames + "\n"); throw new TinkerPatchException( "ignoreWarning is false, but we found animation resource is changed. " + "Please check if any one was used in 'overridePendingTransition' which may leads to crash. " + "If all of them were not used in that method, just add them into 'res { ignoreChangeWarning }' option.\n" + "related res: " + changedAnimResNames); } } } private void getCompressMethodFromApk() { ZipFile zipFile = null; try { zipFile = new ZipFile(config.mNewApkFile); ArrayList sets = new ArrayList<>(); sets.addAll(modifiedSet); sets.addAll(addedSet); ZipEntry zipEntry; for (String name : sets) { zipEntry = zipFile.getEntry(name); if (zipEntry != null && zipEntry.getMethod() == ZipEntry.STORED) { storedSet.add(name); } } } catch (Throwable throwable) { // Ignored. } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { // Ignored. } } } } private void removeIgnoreChangeFile(ArrayList array) { ArrayList removeList = new ArrayList<>(); for (String name : array) { if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) { Logger.e("ignore change resource file: " + name); removeList.add(name); } } array.removeAll(removeList); } private void writeMetaFile(String line) { metaWriter.writeLineToInfoFile(line); } private void writeMetaFile(ArrayList set, int mode) { if (!set.isEmpty()) { String title = ""; switch (mode) { case TypedValue.ADD: title = TypedValue.ADD_TITLE + set.size(); break; case TypedValue.MOD: title = TypedValue.MOD_TITLE + set.size(); break; case TypedValue.LARGE_MOD: title = TypedValue.LARGE_MOD_TITLE + set.size(); break; case TypedValue.DEL: title = TypedValue.DEL_TITLE + set.size(); break; case TypedValue.STORED: title = TypedValue.STORE_TITLE + set.size(); break; default: break; } metaWriter.writeLineToInfoFile(title); for (String name : set) { String line = name; if (mode == TypedValue.LARGE_MOD) { LargeModeInfo info = largeModifiedMap.get(name); line = name + "," + info.md5 + "," + info.crc; } metaWriter.writeLineToInfoFile(line); } } } public ArrayList getDeletedResource(File oldApkDir, File newApkDir) throws IOException { //get deleted resource DeletedResVisitor deletedResVisitor = new DeletedResVisitor(config, newApkDir.toPath(), oldApkDir.toPath()); Files.walkFileTree(oldApkDir.toPath(), deletedResVisitor); return deletedResVisitor.deletedFiles; } public class LargeModeInfo { public File path = null; public long crc; public String md5 = null; } class DeletedResVisitor extends SimpleFileVisitor { Configuration config; Path newApkPath; Path oldApkPath; ArrayList deletedFiles; DeletedResVisitor(Configuration config, Path newPath, Path oldPath) { this.config = config; this.newApkPath = newPath; this.oldApkPath = oldPath; this.deletedFiles = new ArrayList<>(); } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativePath = oldApkPath.relativize(file); Path newPath = newApkPath.resolve(relativePath); String patternKey = relativePath.toString().replace("\\", "/"); if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) { //not contain in new path, is deleted if (!newPath.toFile().exists()) { deletedFiles.add(patternKey); writeResLog(newPath.toFile(), file.toFile(), TypedValue.DEL); } return FileVisitResult.CONTINUE; } return FileVisitResult.CONTINUE; } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/SoDiffDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.info.InfoWriter; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.DiffFactory; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.MD5; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.Utils; import java.io.File; import java.io.IOException; /** * Created by zhangshaowen on 16/2/27. */ public class SoDiffDecoder extends BaseDecoder { private final InfoWriter logWriter; private final InfoWriter metaWriter; public SoDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { super(config); if (metaPath != null) { metaWriter = new InfoWriter(config, config.mTempResultDir + File.separator + metaPath); } else { metaWriter = null; } if (logPath != null) { logWriter = new InfoWriter(config, config.mOutFolder + File.separator + logPath); } else { logWriter = null; } } @Override public void clean() { logWriter.close(); metaWriter.close(); } @Override public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { //first of all, we should check input files if (newFile == null || !newFile.exists()) { return false; } //new add file String newMd5 = MD5.getMD5(newFile); File diffFile = getOutputPath(newFile).toFile(); if (oldFile == null || !oldFile.exists()) { FileOperation.copyFileUsingStream(newFile, diffFile); writeLogFiles(newFile, null, null, newMd5); return true; } //both file length is 0 if (oldFile.length() == 0 && newFile.length() == 0) { return false; } if (oldFile.length() == 0 || newFile.length() == 0) { FileOperation.copyFileUsingStream(newFile, diffFile); writeLogFiles(newFile, null, null, newMd5); return true; } //new add file String oldMd5 = MD5.getMD5(oldFile); if (oldMd5.equals(newMd5)) { return false; } if (!diffFile.getParentFile().exists()) { diffFile.getParentFile().mkdirs(); } diffFile(oldFile, newFile, diffFile); if (Utils.checkBsDiffFileSize(diffFile, newFile)) { writeLogFiles(newFile, oldFile, diffFile, newMd5); } else { FileOperation.copyFileUsingStream(newFile, diffFile); writeLogFiles(newFile, null, null, newMd5); } return true; } @Override public void onAllPatchesStart() throws IOException, TinkerPatchException { } @Override public void onAllPatchesEnd() throws IOException, TinkerPatchException { } protected void writeLogFiles(File newFile, File oldFile, File bsDiff, String newMd5) throws IOException { if (metaWriter == null && logWriter == null) { return; } String parentRelative = getParentRelativePathStringToNewFile(newFile); String relative = getRelativePathStringToNewFile(newFile); if (metaWriter != null) { String fileName = newFile.getName(); String meta; if (bsDiff == null || oldFile == null) { meta = fileName + "," + parentRelative + "," + newMd5 + "," + 0 + "," + 0; } else { String oldCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, relative); if (oldCrc == null || oldCrc.equals("0")) { throw new TinkerPatchException( String.format("can't find zipEntry %s from old apk file %s", relative, config.mOldApkFile.getPath()) ); } meta = fileName + "," + parentRelative + "," + newMd5 + "," + oldCrc + "," + MD5.getMD5(bsDiff); } Logger.d("BsDiffDecoder:write meta file data: %s", meta); metaWriter.writeLineToInfoFile(meta); } if (logWriter != null) { String log = relative + ", oldSize=" + FileOperation.getFileSizes(oldFile) + ", newSize=" + FileOperation.getFileSizes(newFile) + ", diffSize=" + FileOperation.getFileSizes(bsDiff); logWriter.writeLineToInfoFile(log); } } private void diffFile(File oldFile, File newFile, File diffFile) throws IOException { DiffFactory.diffFile(config, oldFile, newFile, diffFile); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/decoder/UniqueDexDiffDecoder.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.decoder; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.TinkerPatchException; import java.io.File; import java.io.IOException; import java.util.ArrayList; /** * Created by zhangshaowen on 16/3/9. */ public class UniqueDexDiffDecoder extends DexDiffDecoder { private ArrayList addedDexFiles; public UniqueDexDiffDecoder(Configuration config, String metaPath, String logPath) throws IOException { super(config, metaPath, logPath); addedDexFiles = new ArrayList<>(); } @Override public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { boolean added = super.patch(oldFile, newFile); if (added) { String name = newFile.getName(); if (addedDexFiles.contains(name)) { throw new TinkerPatchException("illegal dex name, dex name should be unique, dex:" + name); } else { addedDexFiles.add(name); } } return added; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/DexPatchGenerator.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher; import com.tencent.tinker.android.dex.Annotation; import com.tencent.tinker.android.dex.AnnotationSet; import com.tencent.tinker.android.dex.AnnotationSetRefList; import com.tencent.tinker.android.dex.AnnotationsDirectory; import com.tencent.tinker.android.dex.CallSiteId; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.DebugInfoItem; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.EncodedValue; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.MethodHandle; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.StringData; import com.tencent.tinker.android.dex.TypeList; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSetRefListSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationSetSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.AnnotationsDirectorySectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.CallSiteIdSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.ClassDataSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.ClassDefSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.CodeSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.DebugInfoItemSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.DexSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.FieldIdSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.MethodHandleSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.MethodIdSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.ProtoIdSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.StaticValueSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.StringDataSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.TypeIdSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.algorithms.diff.TypeListSectionDiffAlgorithm; import com.tencent.tinker.build.dexpatcher.util.PatternUtils; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; import com.tencent.tinker.commons.dexpatcher.struct.DexPatchFile; import com.tencent.tinker.commons.dexpatcher.struct.PatchOperation; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * Created by tangyinsheng on 2016/6/30. */ public class DexPatchGenerator { private static final String TAG = "DexPatchGenerator"; private final Dex oldDex; private final Dex newDex; private final DexPatcherLogger logger = new DexPatcherLogger(); private DexSectionDiffAlgorithm stringDataSectionDiffAlg; private DexSectionDiffAlgorithm typeIdSectionDiffAlg; private DexSectionDiffAlgorithm protoIdSectionDiffAlg; private DexSectionDiffAlgorithm fieldIdSectionDiffAlg; private DexSectionDiffAlgorithm methodIdSectionDiffAlg; private DexSectionDiffAlgorithm callsiteIdSectionDiffAlg; private DexSectionDiffAlgorithm methodHandleSectionDiffAlg; private DexSectionDiffAlgorithm classDefSectionDiffAlg; private DexSectionDiffAlgorithm typeListSectionDiffAlg; private DexSectionDiffAlgorithm annotationSetRefListSectionDiffAlg; private DexSectionDiffAlgorithm annotationSetSectionDiffAlg; private DexSectionDiffAlgorithm classDataSectionDiffAlg; private DexSectionDiffAlgorithm codeSectionDiffAlg; private DexSectionDiffAlgorithm debugInfoSectionDiffAlg; private DexSectionDiffAlgorithm annotationSectionDiffAlg; private DexSectionDiffAlgorithm encodedArraySectionDiffAlg; private DexSectionDiffAlgorithm annotationsDirectorySectionDiffAlg; private Set additionalRemovingClassPatternSet; private int patchedHeaderOffset = 0; private int patchedStringIdsOffset = 0; private int patchedTypeIdsOffset = 0; private int patchedProtoIdsOffset = 0; private int patchedFieldIdsOffset = 0; private int patchedMethodIdsOffset = 0; private int patchedCallSiteIdsOffset = 0; private int patchedMethodHandlesOffset = 0; private int patchedClassDefsOffset = 0; private int patchedTypeListsOffset = 0; private int patchedAnnotationItemsOffset = 0; private int patchedAnnotationSetItemsOffset = 0; private int patchedAnnotationSetRefListItemsOffset = 0; private int patchedAnnotationsDirectoryItemsOffset = 0; private int patchedDebugInfoItemsOffset = 0; private int patchedCodeItemsOffset = 0; private int patchedClassDataItemsOffset = 0; private int patchedStringDataItemsOffset = 0; private int patchedEncodedArrayItemsOffset = 0; private int patchedMapListOffset = 0; private int patchedDexSize = 0; public DexPatchGenerator(File oldDexFile, File newDexFile) throws IOException { this(new Dex(oldDexFile), new Dex(newDexFile)); } /** * Notice: you should close inputstream manually. */ public DexPatchGenerator(File oldDexFile, InputStream newDexStream) throws IOException { this(new Dex(oldDexFile), new Dex(newDexStream)); } /** * Notice: you should close inputstream manually. */ public DexPatchGenerator(InputStream oldDexStream, InputStream newDexStream) throws IOException { this(new Dex(oldDexStream), new Dex(newDexStream)); } public DexPatchGenerator(Dex oldDex, Dex newDex) { this.oldDex = oldDex; this.newDex = newDex; SparseIndexMap oldToNewIndexMap = new SparseIndexMap(); SparseIndexMap oldToPatchedIndexMap = new SparseIndexMap(); SparseIndexMap newToPatchedIndexMap = new SparseIndexMap(); SparseIndexMap selfIndexMapForSkip = new SparseIndexMap(); additionalRemovingClassPatternSet = new HashSet<>(); this.stringDataSectionDiffAlg = new StringDataSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.typeIdSectionDiffAlg = new TypeIdSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.protoIdSectionDiffAlg = new ProtoIdSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.fieldIdSectionDiffAlg = new FieldIdSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.methodIdSectionDiffAlg = new MethodIdSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.callsiteIdSectionDiffAlg = new CallSiteIdSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.methodHandleSectionDiffAlg = new MethodHandleSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.classDefSectionDiffAlg = new ClassDefSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.typeListSectionDiffAlg = new TypeListSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.annotationSetRefListSectionDiffAlg = new AnnotationSetRefListSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.annotationSetSectionDiffAlg = new AnnotationSetSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.classDataSectionDiffAlg = new ClassDataSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.codeSectionDiffAlg = new CodeSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.debugInfoSectionDiffAlg = new DebugInfoItemSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.annotationSectionDiffAlg = new AnnotationSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.encodedArraySectionDiffAlg = new StaticValueSectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); this.annotationsDirectorySectionDiffAlg = new AnnotationsDirectorySectionDiffAlgorithm( oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip ); } public void addAdditionalRemovingClassPattern(String pattern) { this.additionalRemovingClassPatternSet.add( PatternUtils.dotClassNamePatternToDescriptorRegEx(pattern) ); } public void setAdditionalRemovingClassPatterns(Collection patterns) { for (String pattern : patterns) { this.additionalRemovingClassPatternSet.add( PatternUtils.dotClassNamePatternToDescriptorRegEx(pattern) ); } } public void clearAdditionalRemovingClassPatterns() { this.additionalRemovingClassPatternSet.clear(); } public void setLogger(DexPatcherLogger.IDexPatcherLogger logger) { this.logger.setLoggerImpl(logger); } public void executeAndSaveTo(File file) throws IOException { OutputStream os = null; try { os = new BufferedOutputStream(new FileOutputStream(file)); executeAndSaveTo(os); } finally { IOHelper.closeQuietly(os); } } public void executeAndSaveTo(OutputStream out) throws IOException { // Firstly, collect information of items we want to remove additionally // in new dex and set them to corresponding diff algorithm implementations. Pattern[] classNamePatterns = new Pattern[this.additionalRemovingClassPatternSet.size()]; int classNamePatternCount = 0; for (String regExStr : this.additionalRemovingClassPatternSet) { classNamePatterns[classNamePatternCount++] = Pattern.compile(regExStr); } List typeIdOfClassDefsToRemove = new ArrayList<>(classNamePatternCount); List offsetOfClassDatasToRemove = new ArrayList<>(classNamePatternCount); for (ClassDef classDef : this.newDex.classDefs()) { String typeName = this.newDex.typeNames().get(classDef.typeIndex); for (Pattern pattern : classNamePatterns) { if (pattern.matcher(typeName).matches()) { typeIdOfClassDefsToRemove.add(classDef.typeIndex); offsetOfClassDatasToRemove.add(classDef.classDataOffset); break; } } } ((ClassDefSectionDiffAlgorithm) this.classDefSectionDiffAlg) .setTypeIdOfClassDefsToRemove(typeIdOfClassDefsToRemove); ((ClassDataSectionDiffAlgorithm) this.classDataSectionDiffAlg) .setOffsetOfClassDatasToRemove(offsetOfClassDatasToRemove); // Then, run diff algorithms according to sections' dependencies. // Use size calculated by algorithms above or from dex file definition to // calculate sections' offset and patched dex size. // Calculate header and id sections size, so that we can work out // the base offset of typeLists Section. int patchedheaderSize = SizeOf.HEADER_ITEM; int patchedStringIdsSize = newDex.getTableOfContents().stringIds.size * SizeOf.STRING_ID_ITEM; int patchedTypeIdsSize = newDex.getTableOfContents().typeIds.size * SizeOf.TYPE_ID_ITEM; // Although simulatePatchOperation can calculate this value, since protoIds section // depends on typeLists section, we can't run protoIds Section's simulatePatchOperation // method so far. Instead we calculate protoIds section's size using information in newDex // directly. int patchedProtoIdsSize = newDex.getTableOfContents().protoIds.size * SizeOf.PROTO_ID_ITEM; int patchedFieldIdsSize = newDex.getTableOfContents().fieldIds.size * SizeOf.MEMBER_ID_ITEM; int patchedMethodIdsSize = newDex.getTableOfContents().methodIds.size * SizeOf.MEMBER_ID_ITEM; int patchedCallSiteIdsSize = newDex.getTableOfContents().callSiteIds.size * SizeOf.CALLSITE_ID_ITEM; int patchedMethodHandlesSize = newDex.getTableOfContents().callSiteIds.size * SizeOf.METHOD_HANDLE_ITEM; int patchedClassDefsSize = newDex.getTableOfContents().classDefs.size * SizeOf.CLASS_DEF_ITEM; int patchedIdSectionSize = patchedStringIdsSize + patchedTypeIdsSize + patchedProtoIdsSize + patchedFieldIdsSize + patchedMethodIdsSize + patchedCallSiteIdsSize + patchedMethodHandlesSize + patchedClassDefsSize; this.patchedHeaderOffset = 0; // The diff works on each sections obey such procedure: // 1. Execute diff algorithms to calculate indices of items we need to add, del and replace. // 2. Execute patch algorithm simulation to calculate indices and offsets mappings that is // necessary to next section's diff works. // Immediately do the patch simulation so that we can know: // 1. Indices and offsets mapping between old dex and patched dex. // 2. Indices and offsets mapping between new dex and patched dex. // These information will be used to do next diff works. this.patchedStringIdsOffset = patchedHeaderOffset + patchedheaderSize; if (this.oldDex.getTableOfContents().stringIds.isElementFourByteAligned) { this.patchedStringIdsOffset = SizeOf.roundToTimesOfFour(this.patchedStringIdsOffset); } this.stringDataSectionDiffAlg.execute(); this.patchedStringDataItemsOffset = patchedheaderSize + patchedIdSectionSize; if (this.oldDex.getTableOfContents().stringDatas.isElementFourByteAligned) { this.patchedStringDataItemsOffset = SizeOf.roundToTimesOfFour(this.patchedStringDataItemsOffset); } this.stringDataSectionDiffAlg.simulatePatchOperation(this.patchedStringDataItemsOffset); this.typeIdSectionDiffAlg.execute(); this.patchedTypeIdsOffset = this.patchedStringIdsOffset + patchedStringIdsSize; if (this.oldDex.getTableOfContents().typeIds.isElementFourByteAligned) { this.patchedTypeIdsOffset = SizeOf.roundToTimesOfFour(this.patchedTypeIdsOffset); } this.typeIdSectionDiffAlg.simulatePatchOperation(this.patchedTypeIdsOffset); this.typeListSectionDiffAlg.execute(); this.patchedTypeListsOffset = patchedheaderSize + patchedIdSectionSize + this.stringDataSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().typeLists.isElementFourByteAligned) { this.patchedTypeListsOffset = SizeOf.roundToTimesOfFour(this.patchedTypeListsOffset); } this.typeListSectionDiffAlg.simulatePatchOperation(this.patchedTypeListsOffset); this.protoIdSectionDiffAlg.execute(); this.patchedProtoIdsOffset = this.patchedTypeIdsOffset + patchedTypeIdsSize; if (this.oldDex.getTableOfContents().protoIds.isElementFourByteAligned) { this.patchedProtoIdsOffset = SizeOf.roundToTimesOfFour(this.patchedProtoIdsOffset); } this.protoIdSectionDiffAlg.simulatePatchOperation(this.patchedProtoIdsOffset); this.fieldIdSectionDiffAlg.execute(); this.patchedFieldIdsOffset = this.patchedProtoIdsOffset + patchedProtoIdsSize; if (this.oldDex.getTableOfContents().fieldIds.isElementFourByteAligned) { this.patchedFieldIdsOffset = SizeOf.roundToTimesOfFour(this.patchedFieldIdsOffset); } this.fieldIdSectionDiffAlg.simulatePatchOperation(this.patchedFieldIdsOffset); this.methodIdSectionDiffAlg.execute(); this.patchedMethodIdsOffset = this.patchedFieldIdsOffset + patchedFieldIdsSize; if (this.oldDex.getTableOfContents().methodIds.isElementFourByteAligned) { this.patchedMethodIdsOffset = SizeOf.roundToTimesOfFour(this.patchedMethodIdsOffset); } this.methodIdSectionDiffAlg.simulatePatchOperation(this.patchedMethodIdsOffset); this.methodHandleSectionDiffAlg.execute(); this.patchedMethodHandlesOffset = this.patchedMethodIdsOffset + patchedMethodIdsSize; if (this.oldDex.getTableOfContents().methodHandles.isElementFourByteAligned) { this.patchedMethodHandlesOffset = SizeOf.roundToTimesOfFour(this.patchedMethodHandlesOffset); } this.methodHandleSectionDiffAlg.simulatePatchOperation(this.patchedMethodHandlesOffset); this.annotationSectionDiffAlg.execute(); this.patchedAnnotationItemsOffset = this.patchedTypeListsOffset + this.typeListSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().annotations.isElementFourByteAligned) { this.patchedAnnotationItemsOffset = SizeOf.roundToTimesOfFour(this.patchedAnnotationItemsOffset); } this.annotationSectionDiffAlg.simulatePatchOperation(this.patchedAnnotationItemsOffset); this.annotationSetSectionDiffAlg.execute(); this.patchedAnnotationSetItemsOffset = this.patchedAnnotationItemsOffset + this.annotationSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().annotationSets.isElementFourByteAligned) { this.patchedAnnotationSetItemsOffset = SizeOf.roundToTimesOfFour(this.patchedAnnotationSetItemsOffset); } this.annotationSetSectionDiffAlg.simulatePatchOperation( this.patchedAnnotationSetItemsOffset ); this.annotationSetRefListSectionDiffAlg.execute(); this.patchedAnnotationSetRefListItemsOffset = this.patchedAnnotationSetItemsOffset + this.annotationSetSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().annotationSetRefLists.isElementFourByteAligned) { this.patchedAnnotationSetRefListItemsOffset = SizeOf.roundToTimesOfFour(this.patchedAnnotationSetRefListItemsOffset); } this.annotationSetRefListSectionDiffAlg.simulatePatchOperation( this.patchedAnnotationSetRefListItemsOffset ); this.annotationsDirectorySectionDiffAlg.execute(); this.patchedAnnotationsDirectoryItemsOffset = this.patchedAnnotationSetRefListItemsOffset + this.annotationSetRefListSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().annotationsDirectories.isElementFourByteAligned) { this.patchedAnnotationsDirectoryItemsOffset = SizeOf.roundToTimesOfFour(this.patchedAnnotationsDirectoryItemsOffset); } this.annotationsDirectorySectionDiffAlg.simulatePatchOperation( this.patchedAnnotationsDirectoryItemsOffset ); this.debugInfoSectionDiffAlg.execute(); this.patchedDebugInfoItemsOffset = this.patchedAnnotationsDirectoryItemsOffset + this.annotationsDirectorySectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().debugInfos.isElementFourByteAligned) { this.patchedDebugInfoItemsOffset = SizeOf.roundToTimesOfFour(this.patchedDebugInfoItemsOffset); } this.debugInfoSectionDiffAlg.simulatePatchOperation(this.patchedDebugInfoItemsOffset); this.codeSectionDiffAlg.execute(); this.patchedCodeItemsOffset = this.patchedDebugInfoItemsOffset + this.debugInfoSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().codes.isElementFourByteAligned) { this.patchedCodeItemsOffset = SizeOf.roundToTimesOfFour(this.patchedCodeItemsOffset); } this.codeSectionDiffAlg.simulatePatchOperation(this.patchedCodeItemsOffset); this.classDataSectionDiffAlg.execute(); this.patchedClassDataItemsOffset = this.patchedCodeItemsOffset + this.codeSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().classDatas.isElementFourByteAligned) { this.patchedClassDataItemsOffset = SizeOf.roundToTimesOfFour(this.patchedClassDataItemsOffset); } this.classDataSectionDiffAlg.simulatePatchOperation(this.patchedClassDataItemsOffset); this.encodedArraySectionDiffAlg.execute(); this.patchedEncodedArrayItemsOffset = this.patchedClassDataItemsOffset + this.classDataSectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().encodedArrays.isElementFourByteAligned) { this.patchedEncodedArrayItemsOffset = SizeOf.roundToTimesOfFour(this.patchedEncodedArrayItemsOffset); } this.encodedArraySectionDiffAlg.simulatePatchOperation(this.patchedEncodedArrayItemsOffset); this.callsiteIdSectionDiffAlg.execute(); this.patchedCallSiteIdsOffset = this.patchedEncodedArrayItemsOffset + this.encodedArraySectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().callSiteIds.isElementFourByteAligned) { this.patchedCallSiteIdsOffset = SizeOf.roundToTimesOfFour(this.patchedCallSiteIdsOffset); } this.callsiteIdSectionDiffAlg.simulatePatchOperation(this.patchedCallSiteIdsOffset); this.classDefSectionDiffAlg.execute(); this.patchedClassDefsOffset = this.patchedMethodHandlesOffset + patchedMethodHandlesSize; if (this.oldDex.getTableOfContents().classDefs.isElementFourByteAligned) { this.patchedClassDefsOffset = SizeOf.roundToTimesOfFour(this.patchedClassDefsOffset); } // Calculate any values we still know nothing about them. this.patchedMapListOffset = this.patchedEncodedArrayItemsOffset + this.encodedArraySectionDiffAlg.getPatchedSectionSize(); if (this.oldDex.getTableOfContents().mapList.isElementFourByteAligned) { this.patchedMapListOffset = SizeOf.roundToTimesOfFour(this.patchedMapListOffset); } int patchedMapListSize = newDex.getTableOfContents().mapList.byteCount; this.patchedDexSize = this.patchedMapListOffset + patchedMapListSize; // Finally, write results to patch file. writeResultToStream(out); } private void writeResultToStream(OutputStream os) throws IOException { DexDataBuffer buffer = new DexDataBuffer(); buffer.write(DexPatchFile.MAGIC); buffer.writeShort(DexPatchFile.CURRENT_VERSION); buffer.writeInt(oldDex.getTableOfContents().api); buffer.writeInt(newDex.getTableOfContents().api); buffer.writeInt(this.patchedDexSize); // we will return here to write firstChunkOffset later. int posOfFirstChunkOffsetField = buffer.position(); buffer.writeInt(0); buffer.writeInt(this.patchedStringIdsOffset); buffer.writeInt(this.patchedTypeIdsOffset); buffer.writeInt(this.patchedProtoIdsOffset); buffer.writeInt(this.patchedFieldIdsOffset); buffer.writeInt(this.patchedMethodIdsOffset); buffer.writeInt(this.patchedCallSiteIdsOffset); buffer.writeInt(this.patchedMethodHandlesOffset); buffer.writeInt(this.patchedClassDefsOffset); buffer.writeInt(this.patchedMapListOffset); buffer.writeInt(this.patchedTypeListsOffset); buffer.writeInt(this.patchedAnnotationSetRefListItemsOffset); buffer.writeInt(this.patchedAnnotationSetItemsOffset); buffer.writeInt(this.patchedClassDataItemsOffset); buffer.writeInt(this.patchedCodeItemsOffset); buffer.writeInt(this.patchedStringDataItemsOffset); buffer.writeInt(this.patchedDebugInfoItemsOffset); buffer.writeInt(this.patchedAnnotationItemsOffset); buffer.writeInt(this.patchedEncodedArrayItemsOffset); buffer.writeInt(this.patchedAnnotationsDirectoryItemsOffset); buffer.write(this.oldDex.computeSignature(false)); int firstChunkOffset = buffer.position(); buffer.position(posOfFirstChunkOffsetField); buffer.writeInt(firstChunkOffset); buffer.position(firstChunkOffset); writePatchOperations(buffer, this.stringDataSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.typeIdSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.typeListSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.protoIdSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.fieldIdSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.methodIdSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.methodHandleSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.annotationSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.annotationSetSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.annotationSetRefListSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.annotationsDirectorySectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.debugInfoSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.codeSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.classDataSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.encodedArraySectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.callsiteIdSectionDiffAlg.getPatchOperationList()); writePatchOperations(buffer, this.classDefSectionDiffAlg.getPatchOperationList()); byte[] bufferData = buffer.array(); os.write(bufferData); os.flush(); } private > void writePatchOperations( DexDataBuffer buffer, List> patchOperationList ) { List delOpIndexList = new ArrayList<>(patchOperationList.size()); List addOpIndexList = new ArrayList<>(patchOperationList.size()); List replaceOpIndexList = new ArrayList<>(patchOperationList.size()); List newItemList = new ArrayList<>(patchOperationList.size()); for (PatchOperation patchOperation : patchOperationList) { switch (patchOperation.op) { case PatchOperation.OP_DEL: { delOpIndexList.add(patchOperation.index); break; } case PatchOperation.OP_ADD: { addOpIndexList.add(patchOperation.index); newItemList.add(patchOperation.newItem); break; } case PatchOperation.OP_REPLACE: { replaceOpIndexList.add(patchOperation.index); newItemList.add(patchOperation.newItem); break; } default: break; } } buffer.writeUleb128(delOpIndexList.size()); int lastIndex = 0; for (Integer index : delOpIndexList) { buffer.writeSleb128(index - lastIndex); lastIndex = index; } buffer.writeUleb128(addOpIndexList.size()); lastIndex = 0; for (Integer index : addOpIndexList) { buffer.writeSleb128(index - lastIndex); lastIndex = index; } buffer.writeUleb128(replaceOpIndexList.size()); lastIndex = 0; for (Integer index : replaceOpIndexList) { buffer.writeSleb128(index - lastIndex); lastIndex = index; } for (T newItem : newItemList) { if (newItem instanceof StringData) { buffer.writeStringData((StringData) newItem); } else if (newItem instanceof Integer) { // TypeId item. buffer.writeInt((Integer) newItem); } else if (newItem instanceof TypeList) { buffer.writeTypeList((TypeList) newItem); } else if (newItem instanceof ProtoId) { buffer.writeProtoId((ProtoId) newItem); } else if (newItem instanceof FieldId) { buffer.writeFieldId((FieldId) newItem); } else if (newItem instanceof MethodId) { buffer.writeMethodId((MethodId) newItem); } else if (newItem instanceof CallSiteId) { buffer.writeCallSiteId((CallSiteId) newItem); } else if (newItem instanceof MethodHandle) { buffer.writeMethodHandle((MethodHandle) newItem); } else if (newItem instanceof Annotation) { buffer.writeAnnotation((Annotation) newItem); } else if (newItem instanceof AnnotationSet) { buffer.writeAnnotationSet((AnnotationSet) newItem); } else if (newItem instanceof AnnotationSetRefList) { buffer.writeAnnotationSetRefList((AnnotationSetRefList) newItem); } else if (newItem instanceof AnnotationsDirectory) { buffer.writeAnnotationsDirectory((AnnotationsDirectory) newItem); } else if (newItem instanceof DebugInfoItem) { buffer.writeDebugInfoItem((DebugInfoItem) newItem); } else if (newItem instanceof Code) { buffer.writeCode((Code) newItem); } else if (newItem instanceof ClassData) { buffer.writeClassData((ClassData) newItem); } else if (newItem instanceof EncodedValue) { buffer.writeEncodedArray((EncodedValue) newItem); } else if (newItem instanceof ClassDef) { buffer.writeClassDef((ClassDef) newItem); } else { throw new IllegalStateException( "Unknown item type: " + newItem.getClass() ); } } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Annotation; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class AnnotationSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public AnnotationSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().annotations; } @Override protected Annotation nextItem(DexDataBuffer section) { return section.readAnnotation(); } @Override protected int getItemSize(Annotation item) { return item.byteCountInDex(); } @Override protected Annotation adjustItem(AbstractIndexMap indexMap, Annotation item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapAnnotationOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markAnnotationDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetRefListSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.AnnotationSetRefList; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class AnnotationSetRefListSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public AnnotationSetRefListSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().annotationSetRefLists; } @Override protected AnnotationSetRefList nextItem(DexDataBuffer section) { return section.readAnnotationSetRefList(); } @Override protected int getItemSize(AnnotationSetRefList item) { return item.byteCountInDex(); } @Override protected AnnotationSetRefList adjustItem(AbstractIndexMap indexMap, AnnotationSetRefList item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapAnnotationSetRefListOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markAnnotationSetRefListDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationSetSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.AnnotationSet; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class AnnotationSetSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public AnnotationSetSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().annotationSets; } @Override protected AnnotationSet nextItem(DexDataBuffer section) { return section.readAnnotationSet(); } @Override protected int getItemSize(AnnotationSet item) { return item.byteCountInDex(); } @Override protected AnnotationSet adjustItem(AbstractIndexMap indexMap, AnnotationSet item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapAnnotationSetOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markAnnotationSetDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/AnnotationsDirectorySectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.AnnotationsDirectory; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class AnnotationsDirectorySectionDiffAlgorithm extends DexSectionDiffAlgorithm { public AnnotationsDirectorySectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().annotationsDirectories; } @Override protected AnnotationsDirectory nextItem(DexDataBuffer section) { return section.readAnnotationsDirectory(); } @Override protected int getItemSize(AnnotationsDirectory item) { return item.byteCountInDex(); } @Override protected AnnotationsDirectory adjustItem(AbstractIndexMap indexMap, AnnotationsDirectory item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapAnnotationsDirectoryOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markAnnotationsDirectoryDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/CallSiteIdSectionDiffAlgorithm.java ================================================ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.CallSiteId; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; public class CallSiteIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public CallSiteIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().callSiteIds; } @Override protected CallSiteId nextItem(DexDataBuffer section) { return section.readCallSiteId(); } @Override protected int getItemSize(CallSiteId item) { return item.byteCountInDex(); } @Override protected CallSiteId adjustItem(AbstractIndexMap indexMap, CallSiteId item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { sparseIndexMap.mapCallsiteIds(oldIndex, newIndex); } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markCallsiteDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDataSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Created by tangyinsheng on 2016/6/30. */ public class ClassDataSectionDiffAlgorithm extends DexSectionDiffAlgorithm { private Set offsetOfClassDataToRemoveSet = new HashSet<>(); public ClassDataSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } public void setOffsetOfClassDatasToRemove(Collection offsetOfClassDatasToRemove) { this.offsetOfClassDataToRemoveSet.clear(); this.offsetOfClassDataToRemoveSet.addAll(offsetOfClassDatasToRemove); } public void clearTypeIdOfClassDefsToRemove() { this.offsetOfClassDataToRemoveSet.clear(); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().classDatas; } @Override protected ClassData nextItem(DexDataBuffer section) { return section.readClassData(); } @Override protected int getItemSize(ClassData item) { return item.byteCountInDex(); } @Override protected ClassData adjustItem(AbstractIndexMap indexMap, ClassData item) { return indexMap.adjust(item); } @Override public int getPatchedSectionSize() { // assume each uleb128 field's length may be inflate by 2 bytes. return super.getPatchedSectionSize() + newDex.getTableOfContents().classDatas.size * SizeOf.USHORT; } @Override protected boolean shouldSkipInNewDex(ClassData newItem) { return this.offsetOfClassDataToRemoveSet.contains(newItem.off); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapClassDataOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markClassDataDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ClassDefSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Created by tangyinsheng on 2016/6/30. */ public class ClassDefSectionDiffAlgorithm extends DexSectionDiffAlgorithm { private Set typeIdOfClassDefToRemoveSet = new HashSet<>(); public ClassDefSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } public void setTypeIdOfClassDefsToRemove(Collection typeIdOfClassDefsToRemove) { this.typeIdOfClassDefToRemoveSet.clear(); this.typeIdOfClassDefToRemoveSet.addAll(typeIdOfClassDefsToRemove); } public void clearTypeIdOfClassDefsToRemove() { this.typeIdOfClassDefToRemoveSet.clear(); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().classDefs; } @Override protected ClassDef nextItem(DexDataBuffer section) { return section.readClassDef(); } @Override protected boolean shouldSkipInNewDex(ClassDef newItem) { return this.typeIdOfClassDefToRemoveSet.contains(newItem.typeIndex); } @Override protected int getItemSize(ClassDef item) { return SizeOf.CLASS_DEF_ITEM; } @Override protected ClassDef adjustItem(AbstractIndexMap indexMap, ClassDef item) { return indexMap.adjust(item); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/CodeSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class CodeSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public CodeSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().codes; } @Override protected Code nextItem(DexDataBuffer section) { return section.readCode(); } @Override protected int getItemSize(Code item) { return item.byteCountInDex(); } @Override protected Code adjustItem(AbstractIndexMap indexMap, Code item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapCodeOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markCodeDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DebugInfoItemSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.DebugInfoItem; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class DebugInfoItemSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public DebugInfoItemSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().debugInfos; } @Override protected DebugInfoItem nextItem(DexDataBuffer section) { return section.readDebugInfoItem(); } @Override protected int getItemSize(DebugInfoItem item) { return item.byteCountInDex(); } @Override protected DebugInfoItem adjustItem(AbstractIndexMap indexMap, DebugInfoItem item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapDebugInfoItemOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markDebugInfoItemDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/DexSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.TableOfContents.Section.Item; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.android.dex.util.CompareUtils; import com.tencent.tinker.commons.dexpatcher.struct.PatchOperation; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Created by tangyinsheng on 2016/6/29. */ public abstract class DexSectionDiffAlgorithm> { private static final AbstractMap.SimpleEntry[] EMPTY_ENTRY_ARRAY = new AbstractMap.SimpleEntry[0]; protected final Dex oldDex; protected final Dex newDex; /** * SparseIndexMap for mapping items between old dex and new dex. * e.g. item.oldIndex => item.newIndex */ private final SparseIndexMap oldToNewIndexMap; /** * SparseIndexMap for mapping items between old dex and patched dex. * e.g. item.oldIndex => item.patchedIndex */ private final SparseIndexMap oldToPatchedIndexMap; /** * SparseIndexMap for mapping items between new dex and patched dex. * e.g. item.newIndex => item.newIndexInPatchedDex */ private final SparseIndexMap newToPatchedIndexMap; /** * SparseIndexMap for mapping items in new dex when skip items. */ private final SparseIndexMap selfIndexMapForSkip; private final List> patchOperationList; private final Map> indexToDelOperationMap = new HashMap<>(); private final Map> indexToAddOperationMap = new HashMap<>(); private final Map> indexToReplaceOperationMap = new HashMap<>(); private final Map oldIndexToNewIndexMap = new HashMap<>(); private final Map oldOffsetToNewOffsetMap = new HashMap<>(); private int patchedSectionSize; private Comparator> comparatorForItemDiff = new Comparator>() { @Override public int compare(AbstractMap.SimpleEntry o1, AbstractMap.SimpleEntry o2) { return o1.getValue().compareTo(o2.getValue()); } }; private Comparator> comparatorForPatchOperationOpt = new Comparator>() { @Override public int compare(PatchOperation o1, PatchOperation o2) { if (o1.index != o2.index) { return CompareUtils.sCompare(o1.index, o2.index); } int o1OrderId; switch (o1.op) { case PatchOperation.OP_DEL: o1OrderId = 0; break; case PatchOperation.OP_ADD: o1OrderId = 1; break; case PatchOperation.OP_REPLACE: o1OrderId = 2; break; default: throw new IllegalStateException("unexpected patch operation code: " + o1.op); } int o2OrderId; switch (o2.op) { case PatchOperation.OP_DEL: o2OrderId = 0; break; case PatchOperation.OP_ADD: o2OrderId = 1; break; case PatchOperation.OP_REPLACE: o2OrderId = 2; break; default: throw new IllegalStateException("unexpected patch operation code: " + o2.op); } return CompareUtils.sCompare(o1OrderId, o2OrderId); } }; private AbstractMap.SimpleEntry[] adjustedOldIndexedItemsWithOrigOrder = null; private int oldItemCount = 0; private int newItemCount = 0; public DexSectionDiffAlgorithm( Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip ) { this.oldDex = oldDex; this.newDex = newDex; this.oldToNewIndexMap = oldToNewIndexMap; this.oldToPatchedIndexMap = oldToPatchedIndexMap; this.newToPatchedIndexMap = newToPatchedIndexMap; this.selfIndexMapForSkip = selfIndexMapForSkip; this.patchOperationList = new ArrayList<>(); this.patchedSectionSize = 0; } public List> getPatchOperationList() { return this.patchOperationList; } public int getPatchedSectionSize() { return this.patchedSectionSize; } /** * Get {@code Section} in {@code TableOfContents}. */ protected abstract TableOfContents.Section getTocSection(Dex dex); /** * Get next item in {@code section}. */ protected abstract T nextItem(DexDataBuffer section); /** * Get item size. */ protected abstract int getItemSize(T item); /** * Adjust {@code item} using specific {@code sparseIndexMap} */ protected T adjustItem(AbstractIndexMap indexMap, T item) { return item; } /** * Indicate if {@code item} should be skipped in new dex. */ protected boolean shouldSkipInNewDex(T newItem) { return false; } /** * Update index or offset mapping in {@code sparseIndexMap}. */ protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { // Should override by subclass if needed. } /** * Mark deleted index or offset in {@code sparseIndexMap}. * * Here we mark deleted item for such a case like this: * Item in DebugInfo section reference a string in StringData section * by index X, while in patched dex, the referenced string is removed. * * The {@code sparseIndexMap} must be aware of this case and return -1 * instead of the original value X. * * Further more, the special value -1 is not chosen by our inspiration but * the definition of NO_INDEX in document of dex file format. */ protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { // Should override by subclass if needed. } /** * Adapter method for item's offset fetching, if an item is not * inherited from {@code Item} (which means it is a simple item in dex section * that doesn't need multiple members to describe), this method * return {@code index} instead. */ private int getItemOffsetOrIndex(int index, T item) { if (item instanceof Item) { return ((Item) item).off; } else { return index; } } @SuppressWarnings("unchecked,NewApi") private AbstractMap.SimpleEntry[] collectSectionItems(Dex dex, boolean isOldDex) { TableOfContents.Section tocSec = getTocSection(dex); if (!tocSec.exists()) { return EMPTY_ENTRY_ARRAY; } Dex.Section dexSec = dex.openSection(tocSec); int itemCount = tocSec.size; List> result = new ArrayList<>(itemCount); if (isOldDex) { for (int i = 0; i < itemCount; ++i) { T nextItem = nextItem(dexSec); T adjustedItem = adjustItem(oldToPatchedIndexMap, nextItem); result.add(new AbstractMap.SimpleEntry<>(i, adjustedItem)); } } else { int i = 0; while (i < itemCount) { T nextItem = nextItem(dexSec); int indexBeforeSkip = i; int offsetBeforeSkip = getItemOffsetOrIndex(indexBeforeSkip, nextItem); int indexAfterSkip = indexBeforeSkip; while (indexAfterSkip < itemCount && shouldSkipInNewDex(nextItem)) { if (indexAfterSkip + 1 >= itemCount) { // after skipping last item, nextItem will be null. nextItem = null; } else { nextItem = nextItem(dexSec); } ++indexAfterSkip; } if (nextItem != null) { int offsetAfterSkip = getItemOffsetOrIndex(indexAfterSkip, nextItem); T adjustedItem = adjustItem(newToPatchedIndexMap, adjustItem(selfIndexMapForSkip, nextItem)); int currentOutIndex = result.size(); result.add(new AbstractMap.SimpleEntry<>(currentOutIndex, adjustedItem)); updateIndexOrOffset(selfIndexMapForSkip, indexBeforeSkip, offsetBeforeSkip, indexAfterSkip, offsetAfterSkip); } i = indexAfterSkip; ++i; } } return result.toArray(new AbstractMap.SimpleEntry[0]); } public void execute() { this.patchOperationList.clear(); this.adjustedOldIndexedItemsWithOrigOrder = collectSectionItems(this.oldDex, true); this.oldItemCount = this.adjustedOldIndexedItemsWithOrigOrder.length; AbstractMap.SimpleEntry[] adjustedOldIndexedItems = new AbstractMap.SimpleEntry[this.oldItemCount]; System.arraycopy(this.adjustedOldIndexedItemsWithOrigOrder, 0, adjustedOldIndexedItems, 0, this.oldItemCount); Arrays.sort(adjustedOldIndexedItems, this.comparatorForItemDiff); AbstractMap.SimpleEntry[] adjustedNewIndexedItems = collectSectionItems(this.newDex, false); this.newItemCount = adjustedNewIndexedItems.length; Arrays.sort(adjustedNewIndexedItems, this.comparatorForItemDiff); int oldCursor = 0; int newCursor = 0; while (oldCursor < this.oldItemCount || newCursor < this.newItemCount) { if (oldCursor >= this.oldItemCount) { // rest item are all newItem. while (newCursor < this.newItemCount) { AbstractMap.SimpleEntry newIndexedItem = adjustedNewIndexedItems[newCursor++]; this.patchOperationList.add(new PatchOperation<>(PatchOperation.OP_ADD, newIndexedItem.getKey(), newIndexedItem.getValue())); } } else if (newCursor >= newItemCount) { // rest item are all oldItem. while (oldCursor < oldItemCount) { AbstractMap.SimpleEntry oldIndexedItem = adjustedOldIndexedItems[oldCursor++]; int deletedIndex = oldIndexedItem.getKey(); int deletedOffset = getItemOffsetOrIndex(deletedIndex, oldIndexedItem.getValue()); this.patchOperationList.add(new PatchOperation(PatchOperation.OP_DEL, deletedIndex)); markDeletedIndexOrOffset(this.oldToPatchedIndexMap, deletedIndex, deletedOffset); } } else { AbstractMap.SimpleEntry oldIndexedItem = adjustedOldIndexedItems[oldCursor]; AbstractMap.SimpleEntry newIndexedItem = adjustedNewIndexedItems[newCursor]; int cmpRes = oldIndexedItem.getValue().compareTo(newIndexedItem.getValue()); if (cmpRes < 0) { int deletedIndex = oldIndexedItem.getKey(); int deletedOffset = getItemOffsetOrIndex(deletedIndex, oldIndexedItem.getValue()); this.patchOperationList.add(new PatchOperation(PatchOperation.OP_DEL, deletedIndex)); markDeletedIndexOrOffset(this.oldToPatchedIndexMap, deletedIndex, deletedOffset); ++oldCursor; } else if (cmpRes > 0) { this.patchOperationList.add(new PatchOperation<>(PatchOperation.OP_ADD, newIndexedItem.getKey(), newIndexedItem.getValue())); ++newCursor; } else { int oldIndex = oldIndexedItem.getKey(); int newIndex = newIndexedItem.getKey(); int oldOffset = getItemOffsetOrIndex(oldIndexedItem.getKey(), oldIndexedItem.getValue()); int newOffset = getItemOffsetOrIndex(newIndexedItem.getKey(), newIndexedItem.getValue()); if (oldIndex != newIndex) { this.oldIndexToNewIndexMap.put(oldIndex, newIndex); } if (oldOffset != newOffset) { this.oldOffsetToNewOffsetMap.put(oldOffset, newOffset); } ++oldCursor; ++newCursor; } } } // So far all diff works are done. Then we perform some optimize works. // detail: {OP_DEL idx} followed by {OP_ADD the_same_idx newItem} // will be replaced by {OP_REPLACE idx newItem} Collections.sort(this.patchOperationList, comparatorForPatchOperationOpt); Iterator> patchOperationIt = this.patchOperationList.iterator(); PatchOperation prevPatchOperation = null; while (patchOperationIt.hasNext()) { PatchOperation patchOperation = patchOperationIt.next(); if (prevPatchOperation != null && prevPatchOperation.op == PatchOperation.OP_DEL && patchOperation.op == PatchOperation.OP_ADD ) { if (prevPatchOperation.index == patchOperation.index) { prevPatchOperation.op = PatchOperation.OP_REPLACE; prevPatchOperation.newItem = patchOperation.newItem; patchOperationIt.remove(); prevPatchOperation = null; } else { prevPatchOperation = patchOperation; } } else { prevPatchOperation = patchOperation; } } // Finally we record some information for the final calculations. patchOperationIt = this.patchOperationList.iterator(); while (patchOperationIt.hasNext()) { PatchOperation patchOperation = patchOperationIt.next(); switch (patchOperation.op) { case PatchOperation.OP_DEL: { indexToDelOperationMap.put(patchOperation.index, patchOperation); break; } case PatchOperation.OP_ADD: { indexToAddOperationMap.put(patchOperation.index, patchOperation); break; } case PatchOperation.OP_REPLACE: { indexToReplaceOperationMap.put(patchOperation.index, patchOperation); break; } default: { break; } } } } public void simulatePatchOperation(int baseOffset) { boolean isNeedToMakeAlign = getTocSection(this.oldDex).isElementFourByteAligned; int oldIndex = 0; int patchedIndex = 0; int patchedOffset = baseOffset; while (oldIndex < this.oldItemCount || patchedIndex < this.newItemCount) { if (this.indexToAddOperationMap.containsKey(patchedIndex)) { PatchOperation patchOperation = this.indexToAddOperationMap.get(patchedIndex); if (isNeedToMakeAlign) { patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); } T newItem = patchOperation.newItem; int itemSize = getItemSize(newItem); updateIndexOrOffset( this.newToPatchedIndexMap, 0, getItemOffsetOrIndex(patchOperation.index, newItem), 0, patchedOffset ); ++patchedIndex; patchedOffset += itemSize; } else if (this.indexToReplaceOperationMap.containsKey(patchedIndex)) { PatchOperation patchOperation = this.indexToReplaceOperationMap.get(patchedIndex); if (isNeedToMakeAlign) { patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); } T newItem = patchOperation.newItem; int itemSize = getItemSize(newItem); updateIndexOrOffset( this.newToPatchedIndexMap, 0, getItemOffsetOrIndex(patchOperation.index, newItem), 0, patchedOffset ); ++patchedIndex; patchedOffset += itemSize; } else if (this.indexToDelOperationMap.containsKey(oldIndex)) { ++oldIndex; } else if (this.indexToReplaceOperationMap.containsKey(oldIndex)) { ++oldIndex; } else if (oldIndex < this.oldItemCount) { if (isNeedToMakeAlign) { patchedOffset = SizeOf.roundToTimesOfFour(patchedOffset); } T oldItem = this.adjustedOldIndexedItemsWithOrigOrder[oldIndex].getValue(); int itemSize = getItemSize(oldItem); int oldOffset = getItemOffsetOrIndex(oldIndex, oldItem); updateIndexOrOffset( this.oldToPatchedIndexMap, oldIndex, oldOffset, patchedIndex, patchedOffset ); int newIndex = oldIndex; if (this.oldIndexToNewIndexMap.containsKey(oldIndex)) { newIndex = this.oldIndexToNewIndexMap.get(oldIndex); } int newOffset = oldOffset; if (this.oldOffsetToNewOffsetMap.containsKey(oldOffset)) { newOffset = this.oldOffsetToNewOffsetMap.get(oldOffset); } updateIndexOrOffset( this.newToPatchedIndexMap, newIndex, newOffset, patchedIndex, patchedOffset ); ++oldIndex; ++patchedIndex; patchedOffset += itemSize; } } this.patchedSectionSize = SizeOf.roundToTimesOfFour(patchedOffset - baseOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/FieldIdSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class FieldIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public FieldIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().fieldIds; } @Override protected FieldId nextItem(DexDataBuffer section) { return section.readFieldId(); } @Override protected int getItemSize(FieldId item) { return SizeOf.MEMBER_ID_ITEM; } @Override protected FieldId adjustItem(AbstractIndexMap indexMap, FieldId item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldIndex != newIndex) { sparseIndexMap.mapFieldIds(oldIndex, newIndex); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markFieldIdDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/MethodHandleSectionDiffAlgorithm.java ================================================ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.MethodHandle; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; public class MethodHandleSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public MethodHandleSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().methodHandles; } @Override protected MethodHandle nextItem(DexDataBuffer section) { return section.readMethodHandle(); } @Override protected int getItemSize(MethodHandle item) { return item.byteCountInDex(); } @Override protected MethodHandle adjustItem(AbstractIndexMap indexMap, MethodHandle item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { sparseIndexMap.mapMethodHandleIds(oldIndex, newIndex); } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markMethodHandleDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/MethodIdSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class MethodIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public MethodIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().methodIds; } @Override protected MethodId nextItem(DexDataBuffer section) { return section.readMethodId(); } @Override protected int getItemSize(MethodId item) { return SizeOf.MEMBER_ID_ITEM; } @Override protected MethodId adjustItem(AbstractIndexMap indexMap, MethodId item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldIndex != newIndex) { sparseIndexMap.mapMethodIds(oldIndex, newIndex); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markMethodIdDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/ProtoIdSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class ProtoIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public ProtoIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().protoIds; } @Override protected ProtoId nextItem(DexDataBuffer section) { return section.readProtoId(); } @Override protected int getItemSize(ProtoId item) { return SizeOf.PROTO_ID_ITEM; } @Override protected ProtoId adjustItem(AbstractIndexMap indexMap, ProtoId item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldIndex != newIndex) { sparseIndexMap.mapProtoIds(oldIndex, newIndex); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markProtoIdDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StaticValueSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.EncodedValue; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class StaticValueSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public StaticValueSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().encodedArrays; } @Override protected EncodedValue nextItem(DexDataBuffer section) { return section.readEncodedArray(); } @Override protected int getItemSize(EncodedValue item) { return item.byteCountInDex(); } @Override protected EncodedValue adjustItem(AbstractIndexMap indexMap, EncodedValue item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapStaticValuesOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markStaticValuesDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/StringDataSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.StringData; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class StringDataSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public StringDataSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().stringDatas; } @Override protected StringData nextItem(DexDataBuffer section) { return section.readStringData(); } @Override protected int getItemSize(StringData item) { return item.byteCountInDex(); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldIndex != newIndex) { sparseIndexMap.mapStringIds(oldIndex, newIndex); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markStringIdDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeIdSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.SizeOf; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class TypeIdSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public TypeIdSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().typeIds; } @Override protected Integer nextItem(DexDataBuffer section) { return section.readInt(); } @Override protected int getItemSize(Integer item) { return SizeOf.UINT; } @Override protected Integer adjustItem(AbstractIndexMap indexMap, Integer item) { return indexMap.adjustStringIndex(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldIndex != newIndex) { sparseIndexMap.mapTypeIds(oldIndex, newIndex); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markTypeIdDeleted(deletedIndex); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/algorithms/diff/TypeListSectionDiffAlgorithm.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.algorithms.diff; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.TypeList; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.commons.dexpatcher.util.AbstractIndexMap; import com.tencent.tinker.commons.dexpatcher.util.SparseIndexMap; /** * Created by tangyinsheng on 2016/6/30. */ public class TypeListSectionDiffAlgorithm extends DexSectionDiffAlgorithm { public TypeListSectionDiffAlgorithm(Dex oldDex, Dex newDex, SparseIndexMap oldToNewIndexMap, SparseIndexMap oldToPatchedIndexMap, SparseIndexMap newToPatchedIndexMap, SparseIndexMap selfIndexMapForSkip) { super(oldDex, newDex, oldToNewIndexMap, oldToPatchedIndexMap, newToPatchedIndexMap, selfIndexMapForSkip); } @Override protected TableOfContents.Section getTocSection(Dex dex) { return dex.getTableOfContents().typeLists; } @Override protected TypeList nextItem(DexDataBuffer section) { return section.readTypeList(); } @Override protected int getItemSize(TypeList item) { return item.byteCountInDex(); } @Override protected TypeList adjustItem(AbstractIndexMap indexMap, TypeList item) { return indexMap.adjust(item); } @Override protected void updateIndexOrOffset(SparseIndexMap sparseIndexMap, int oldIndex, int oldOffset, int newIndex, int newOffset) { if (oldOffset != newOffset) { sparseIndexMap.mapTypeListOffset(oldOffset, newOffset); } } @Override protected void markDeletedIndexOrOffset(SparseIndexMap sparseIndexMap, int deletedIndex, int deletedOffset) { sparseIndexMap.markTypeListDeleted(deletedOffset); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/ChangedClassesDexClassInfoCollector.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.util; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dx.instruction.InstructionCodec; import com.tencent.tinker.android.dx.instruction.InstructionReader; import com.tencent.tinker.android.dx.instruction.InstructionVisitor; import com.tencent.tinker.android.dx.instruction.ShortArrayCodeInput; import com.tencent.tinker.build.util.DexClassesComparator; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; import java.io.EOFException; import java.util.Collection; import java.util.HashSet; import java.util.Set; import static com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; import static com.tencent.tinker.build.util.DexClassesComparator.DexGroup; /** * Created by tangyinsheng on 2017/2/26. */ public class ChangedClassesDexClassInfoCollector { private static final String TAG = "ChangedClassesDexClassInfoCollector"; private static final DexPatcherLogger LOGGER = new DexPatcherLogger(); private final Set excludedClassPatterns = new HashSet<>(); private boolean includeRefererToRefererAffectedClasses = false; public ChangedClassesDexClassInfoCollector setExcludedClassPatterns(Collection loaderClassPatterns) { this.excludedClassPatterns.clear(); this.excludedClassPatterns.addAll(loaderClassPatterns); return this; } public ChangedClassesDexClassInfoCollector clearExcludedClassPatterns() { this.excludedClassPatterns.clear(); return this; } public ChangedClassesDexClassInfoCollector setLogger(DexPatcherLogger.IDexPatcherLogger loggerImpl) { LOGGER.setLoggerImpl(loggerImpl); return this; } public ChangedClassesDexClassInfoCollector setIncludeRefererToRefererAffectedClasses(boolean enabled) { this.includeRefererToRefererAffectedClasses = enabled; return this; } public Set doCollect(DexGroup oldDexGroup, DexGroup newDexGroup) { final Set classDescsInResult = new HashSet<>(); final Set result = new HashSet<>(); DexClassesComparator dexClassCmptor = new DexClassesComparator("*"); dexClassCmptor.setCompareMode(DexClassesComparator.COMPARE_MODE_NORMAL); dexClassCmptor.setIgnoredRemovedClassDescPattern(excludedClassPatterns); dexClassCmptor.setLogger(LOGGER.getLoggerImpl()); dexClassCmptor.startCheck(oldDexGroup, newDexGroup); // So far we collected infos of all added, changed, and deleted classes. result.addAll(dexClassCmptor.getAddedClassInfos()); final Collection changedClassInfos = dexClassCmptor.getChangedClassDescToInfosMap().values(); for (DexClassInfo[] oldAndNewInfoPair : changedClassInfos) { final DexClassInfo newClassInfo = oldAndNewInfoPair[1]; LOGGER.i(TAG, "Add class %s to changed classes dex.", newClassInfo.classDesc); result.add(newClassInfo); } for (DexClassInfo classInfo : result) { classDescsInResult.add(classInfo.classDesc); } if (includeRefererToRefererAffectedClasses) { // Then we also need to add classes who refer to classes with referrer // affected changes to the result. (referrer affected change means the changes // that may cause referrer refer to wrong target.) dexClassCmptor.setCompareMode(DexClassesComparator.COMPARE_MODE_REFERRER_AFFECTED_CHANGE_ONLY); dexClassCmptor.startCheck(oldDexGroup, newDexGroup); Set referrerAffectedChangedClassDescs = dexClassCmptor.getChangedClassDescToInfosMap().keySet(); Set oldClassInfos = oldDexGroup.getClassInfosInDexesWithDuplicateCheck(); for (DexClassInfo oldClassInfo : oldClassInfos) { if (!classDescsInResult.contains(oldClassInfo.classDesc) && isClassReferToAnyClasses(oldClassInfo, referrerAffectedChangedClassDescs)) { LOGGER.i(TAG, "Add class %s in old dex to changed classes dex since it is affected by modified referee.", oldClassInfo.classDesc); result.add(oldClassInfo); } } } return result; } private boolean isClassReferToAnyClasses(DexClassInfo classInfo, Set refereeClassDescs) { if (classInfo.classDef.classDataOffset == ClassDef.NO_OFFSET) { return false; } ClassData classData = classInfo.owner.readClassData(classInfo.classDef); for (ClassData.Method method : classData.directMethods) { if (isMethodReferToAnyClasses(classInfo, method, refereeClassDescs)) { return true; } } for (ClassData.Method method : classData.virtualMethods) { if (isMethodReferToAnyClasses(classInfo, method, refereeClassDescs)) { return true; } } return false; } private boolean isMethodReferToAnyClasses(DexClassInfo classInfo, ClassData.Method method, Set refereeClassDescs) { if (method.codeOffset == ClassDef.NO_OFFSET) { return false; } Code methodCode = classInfo.owner.readCode(method); InstructionReader ir = new InstructionReader(new ShortArrayCodeInput(methodCode.instructions)); ReferToClassesCheckVisitor rtcv = new ReferToClassesCheckVisitor(classInfo.owner, method, refereeClassDescs); try { ir.accept(rtcv); } catch (EOFException e) { // Should not be here. } return rtcv.isReferToAnyRefereeClasses; } private static class ReferToClassesCheckVisitor extends InstructionVisitor { private final Dex owner; private final ClassData.Method method; private final Collection refereeClassDescs; private boolean isReferToAnyRefereeClasses = false; ReferToClassesCheckVisitor(Dex owner, ClassData.Method method, Collection refereeClassDescs) { super(null); this.owner = owner; this.method = method; this.refereeClassDescs = refereeClassDescs; } @Override public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { processIndexByType(index, indexType); } @Override public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { processIndexByType(index, indexType); } @Override public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { processIndexByType(index, indexType); } @Override public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { processIndexByType(index, indexType); } @Override public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { processIndexByType(index, indexType); } @Override public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { processIndexByType(index, indexType); } @Override public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { processIndexByType(index, indexType); } private void processIndexByType(int index, int indexType) { String typeName = null; String refInfoInLog = null; switch (indexType) { case InstructionCodec.INDEX_TYPE_TYPE_REF: { typeName = owner.typeNames().get(index); refInfoInLog = "init referrer-affected class"; break; } case InstructionCodec.INDEX_TYPE_FIELD_REF: { final FieldId fieldId = owner.fieldIds().get(index); typeName = owner.typeNames().get(fieldId.declaringClassIndex); refInfoInLog = "referencing to field: " + owner.strings().get(fieldId.nameIndex); break; } case InstructionCodec.INDEX_TYPE_METHOD_REF: { final MethodId methodId = owner.methodIds().get(index); typeName = owner.typeNames().get(methodId.declaringClassIndex); refInfoInLog = "invoking method: " + getMethodProtoTypeStr(methodId); break; } default: { break; } } if (typeName != null && refereeClassDescs.contains(typeName)) { MethodId methodId = owner.methodIds().get(method.methodIndex); LOGGER.i( TAG, "Method %s in class %s referenced referrer-affected class %s by %s", getMethodProtoTypeStr(methodId), owner.typeNames().get(methodId.declaringClassIndex), typeName, refInfoInLog ); isReferToAnyRefereeClasses = true; } } private String getMethodProtoTypeStr(MethodId methodId) { StringBuilder strBuilder = new StringBuilder(); strBuilder.append(owner.strings().get(methodId.nameIndex)); ProtoId protoId = owner.protoIds().get(methodId.protoIndex); strBuilder.append('('); short[] paramTypeIds = owner.parameterTypeIndicesFromMethodId(methodId); for (short typeId : paramTypeIds) { strBuilder.append(owner.typeNames().get(typeId)); } strBuilder.append(')').append(owner.typeNames().get(protoId.returnTypeIndex)); return strBuilder.toString(); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/dexpatcher/util/PatternUtils.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.util; /** * Created by tangyinsheng on 2016/4/8. */ public class PatternUtils { public static String dotClassNamePatternToDescriptorRegEx(String dotPattern) { if (dotPattern.startsWith("L") && dotPattern.endsWith(";") || dotPattern.startsWith("[")) { return dotPattern.replace('.', '/').replace("[", "\\["); } String descriptor = dotPattern.replace('.', '/'); StringBuilder sb = new StringBuilder(); int i; for (i = dotPattern.length() - 1; i >= 1; i -= 2) { char ch = dotPattern.charAt(i); char prevCh = dotPattern.charAt(i - 1); if (prevCh == '[' && ch == ']') { sb.append("\\["); } else { break; } } descriptor = descriptor.substring(0, i + 1); if ("void".equals(descriptor)) { descriptor = "V"; sb.append(descriptor); } else if ("boolean".equals(descriptor)) { descriptor = "Z"; sb.append(descriptor); } else if ("byte".equals(descriptor)) { descriptor = "B"; sb.append(descriptor); } else if ("short".equals(descriptor)) { descriptor = "S"; sb.append(descriptor); } else if ("char".equals(descriptor)) { descriptor = "C"; sb.append(descriptor); } else if ("int".equals(descriptor)) { descriptor = "I"; sb.append(descriptor); } else if ("long".equals(descriptor)) { descriptor = "J"; sb.append(descriptor); } else if ("float".equals(descriptor)) { descriptor = "F"; sb.append(descriptor); } else if ("double".equals(descriptor)) { descriptor = "D"; sb.append(descriptor); } else { sb.append('L').append(descriptor); if (!descriptor.endsWith(";")) { sb.append(';'); } } String regEx = sb.toString(); regEx = regEx.replace("*", ".*"); regEx = regEx.replace("?", ".?"); regEx = regEx.replace("$", "\\$"); regEx = '^' + regEx + '$'; return regEx; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/immutable/ClassSimDef.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.immutable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.HashSet; public class ClassSimDef { int methodCount; int fieldCount; byte[] bytes; HashSet refFieldSet; HashSet refMtdSet; public ClassSimDef(byte[] bytes, HashSet refFieldSet, HashSet refMtdSet) { this.bytes = bytes; this.refFieldSet = refFieldSet; this.refMtdSet = refMtdSet; init(); } public void init() { methodCount = 0; fieldCount = 0; ClassReader cr = new ClassReader(bytes); ClassVisitor cv = new ClassVisitor(Opcodes.ASM4) { String className; @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { className = name; super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String mtdName, String mtdDesc, String mtdSig, String[] exceptions) { String defMtd = className + ":" + mtdName + ":" + mtdDesc; if (!refMtdSet.contains(defMtd)) { refMtdSet.add(defMtd); methodCount++; } MethodVisitor mv = super.visitMethod(access, mtdName, mtdDesc, mtdSig, exceptions); mv = new MethodVisitor(Opcodes.ASM4, mv) { @Override public void visitFieldInsn(int opcode, String owner, String fName, String fDesc) { String invokeField = owner + ":" + fName + ":" + fDesc; if (!refFieldSet.contains(invokeField)) { refFieldSet.add(invokeField); fieldCount++; } super.visitFieldInsn(opcode, owner, fName, fDesc); } @Override public void visitMethodInsn(int opcode, String owner, String mName, String mDesc) { String invokeMtd = owner + ":" + mName + ":" + mDesc; if (!refMtdSet.contains(invokeMtd)) { refMtdSet.add(invokeMtd); methodCount++; } super.visitMethodInsn(opcode, owner, mName, mDesc); } }; return mv; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { String fieldDesc = className + ":" + name + ":" + desc; if (!refFieldSet.contains(fieldDesc)) { refFieldSet.add(fieldDesc); fieldCount++; } return super.visitField(access, name, desc, signature, value); } }; cr.accept(cv, 0); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/immutable/DexRefData.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.immutable; import java.util.HashSet; import java.util.Set; public class DexRefData { int methodNum; int fieldNum; public Set refFields; public Set refMtds; DexRefData() { this(0, 0); } DexRefData(int methodNum, int fieldNum) { this.methodNum = methodNum; this.fieldNum = fieldNum; refFields = new HashSet<>(); refMtds = new HashSet<>(); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/InfoWriter.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.info; import com.tencent.tinker.build.patch.Configuration; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.List; /** * Created by zhangshaowen on 16/3/8. */ public class InfoWriter { protected final Configuration config; /** * infoFile, output info */ protected final String infoPath; protected final File infoFile; /** * 首次使用时初始化 */ protected Writer infoWrite; public InfoWriter(Configuration config, String infoPath) throws IOException { this.config = config; this.infoPath = infoPath; if (infoPath != null) { this.infoFile = new File(infoPath); if (!infoFile.getParentFile().exists()) { infoFile.getParentFile().mkdirs(); } } else { this.infoFile = null; } } public Configuration getConfig() { return config; } public void writeLinesToInfoFile(List lines) throws IOException { for (String line : lines) { writeLineToInfoFile(line); } } public void writeLineToInfoFile(String line) { if (infoPath == null || line == null || line.length() == 0) { return; } try { checkWriter(); infoWrite.write(line); infoWrite.write("\n"); infoWrite.flush(); } catch (Exception e) { throw new RuntimeException("write info file error, infoPath:" + infoPath + " content:" + line, e); } } private void checkWriter() throws IOException { if (infoWrite == null) { this.infoWrite = new BufferedWriter(new FileWriter(infoFile, false)); } } public void close() { try { if (infoWrite != null) infoWrite.close(); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfo.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.info; import com.tencent.tinker.build.patch.Configuration; /** * Created by zhangshaowen on 16/3/8. */ public class PatchInfo { private final PatchInfoGen infoGen; public PatchInfo(Configuration config) { infoGen = new PatchInfoGen(config); } /** * gen the meta file txt * such as rev, version ... * file version, hotpatch version class */ public void gen() throws Exception { infoGen.gen(); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/info/PatchInfoGen.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.info; import com.tencent.tinker.build.apkparser.AndroidParser; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.ParseException; import java.util.Properties; import tinker.net.dongliu.apk.parser.utils.Utils; /** * Created by zhangshaowen on 16/3/8. */ public class PatchInfoGen { private final Configuration config; private final File packageInfoFile; public PatchInfoGen(Configuration config) { this.config = config; packageInfoFile = new File(config.mTempResultDir + File.separator + "assets" + File.separator + TypedValue.PACKAGE_META_FILE); } private void addTinkerID() throws IOException, ParseException { if (!config.mPackageFields.containsKey(TypedValue.TINKER_ID)) { AndroidParser oldAndroidManifest = AndroidParser.getAndroidManifest(config.mOldApkFile); String tinkerID = oldAndroidManifest.metaDatas.get(TypedValue.TINKER_ID); if (tinkerID == null) { throw new TinkerPatchException("can't find TINKER_ID from the old apk manifest file, it must be set!"); } config.mPackageFields.put(TypedValue.TINKER_ID, tinkerID); } if (!config.mPackageFields.containsKey(TypedValue.NEW_TINKER_ID)) { AndroidParser newAndroidManifest = AndroidParser.getAndroidManifest(config.mNewApkFile); String tinkerID = newAndroidManifest.metaDatas.get(TypedValue.TINKER_ID); if (tinkerID == null) { throw new TinkerPatchException("can't find TINKER_ID from the new apk manifest file, it must be set!"); } config.mPackageFields.put(TypedValue.NEW_TINKER_ID, tinkerID); } } private void addProtectedAppFlag() { // If user happens to specify a value with this key, just override it for logic correctness. config.mPackageFields.put(TypedValue.PKGMETA_KEY_IS_PROTECTED_APP, config.mIsProtectedApp ? "1" : "0"); } private void addFilePatchFlag() { // If use custom file patcher config.mPackageFields.put(TypedValue.PKGMETA_KEY_USE_CUSTOM_FILE_PATCH, Utils.isEmpty(config.mCustomDiffPath) ? "0" : "1" ); } public void gen() throws Exception { addTinkerID(); addProtectedAppFlag(); addFilePatchFlag(); Properties newProperties = new Properties(); for (String key : config.mPackageFields.keySet()) { newProperties.put(key, config.mPackageFields.get(key)); } String comment = "base package config field"; OutputStream os = null; try { os = new BufferedOutputStream(new FileOutputStream(packageInfoFile, false)); newProperties.store(os, comment); } finally { IOHelper.closeQuietly(os); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Configuration.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.patch; import com.tencent.tinker.build.util.FileOperation; import com.tencent.tinker.build.util.TinkerPatchException; import com.tencent.tinker.build.util.TypedValue; import com.tencent.tinker.build.util.Utils; import com.tencent.tinker.commons.util.IOHelper; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * @author zhangshaowen * do not use Logger here */ public class Configuration { protected static final String TAG_ISSUE = "issue"; protected static final String DEX_ISSUE = "dex"; protected static final String SO_ISSUE = "lib"; protected static final String RES_ISSUE = "resource"; protected static final String ARKHOT_ISSUE = "arkHot"; protected static final String SIGN_ISSUE = "sign"; protected static final String PACKAGE_CONFIG_ISSUE = "packageConfig"; protected static final String PROPERTY_ISSUE = "property"; protected static final String ATTR_ID = "id"; protected static final String ATTR_VALUE = "value"; protected static final String ATTR_NAME = "name"; protected static final String ATTR_IGNORE_WARNING = "ignoreWarning"; protected static final String ATTR_ALLOW_LOADER_IN_ANY_DEX = "allowLoaderInAnyDex"; protected static final String ATTR_REMOVE_LOADER_FOR_ALL_DEX = "removeLoaderForAllDex"; protected static final String ATTR_IS_PROTECTED_APP = "isProtectedApp"; protected static final String ATTR_SUPPORT_HOTPLUG_COMPONENT = "supportHotplugComponent"; protected static final String ATTR_USE_SIGN = "useSign"; protected static final String ATTR_SEVEN_ZIP_PATH = "sevenZipPath"; protected static final String ATTR_CUSTOM_DIFF_PATH = "customPath"; protected static final String ATTR_CUSTOM_DIFF_PATH_ARGS = "customPathArgs"; protected static final String ATTR_DEX_MODE = "dexMode"; protected static final String ATTR_PATTERN = "pattern"; protected static final String ATTR_IGNORE_CHANGE = "ignoreChange"; protected static final String ATTR_IGNORE_CHANGE_WARNING = "ignoreChangeWarning"; protected static final String ATTR_RES_LARGE_MOD = "largeModSize"; protected static final String ATTR_ARKHOT_PATH = "path"; protected static final String ATTR_ARKHOT_NAME = "name"; protected static final String ATTR_LOADER = "loader"; protected static final String ATTR_CONFIG_FIELD = "configField"; protected static final String ATTR_SIGN_FILE_PATH = "path"; protected static final String ATTR_SIGN_FILE_KEYPASS = "keypass"; protected static final String ATTR_SIGN_FILE_STOREPASS = "storepass"; protected static final String ATTR_SIGN_FILE_ALIAS = "alias"; /** * base config data */ public String mOldApkPath; public String mNewApkPath; public String mOutFolder; public File mOldApkFile; public File mNewApkFile; public boolean mIgnoreWarning; public boolean mAllowLoaderInAnyDex; public boolean mIsProtectedApp; public boolean mRemoveLoaderForAllDex; public boolean mSupportHotplugComponent; /** * lib config */ public HashSet mSoFilePattern; /** * dex config */ public HashSet mDexFilePattern; public HashSet mDexLoaderPattern; public HashSet mDexIgnoreWarningLoaderPattern; public boolean mDexRaw; /** * resource config */ public HashSet mResFilePattern; public HashSet mResIgnoreChangePattern; public HashSet mResIgnoreChangeWarningPattern; public HashSet mResRawPattern; public int mLargeModSize; /** * only gradle have the param */ public boolean mUseApplyResource; /** * package file config */ public HashMap mPackageFields; /** * sevenZip path config */ public String mSevenZipPath; /** * custom diff path config */ public String mCustomDiffPath; /** * custom diff path config */ public String mCustomDiffPathArgs; /** * sign data */ public boolean mUseSignAPk; public File mSignatureFile; public String mKeyPass; public String mStoreAlias; public String mStorePass; /** * temp files */ public File mTempResultDir; public File mTempUnzipOldDir; public File mTempUnzipNewDir; public boolean mUsingGradle; /** * ark patch */ public String mArkHotPatchPath; public String mArkHotPatchName; /** * use by command line with xml config */ public Configuration(File config, File outputFile, File oldApkFile, File newApkFile) throws IOException, ParserConfigurationException, SAXException, TinkerPatchException { mUsingGradle = false; mSoFilePattern = new HashSet<>(); mDexFilePattern = new HashSet<>(); mDexLoaderPattern = new HashSet<>(); mDexIgnoreWarningLoaderPattern = new HashSet<>(); mResFilePattern = new HashSet<>(); mResRawPattern = new HashSet<>(); mResIgnoreChangePattern = new HashSet<>(); mResIgnoreChangeWarningPattern = new HashSet<>(); mPackageFields = new HashMap<>(); mOutFolder = outputFile.getAbsolutePath(); FileOperation.cleanDir(outputFile); mOldApkFile = oldApkFile; mOldApkPath = oldApkFile.getAbsolutePath(); mNewApkFile = newApkFile; mNewApkPath = newApkFile.getAbsolutePath(); mLargeModSize = 100; readXmlConfig(config); createTempDirectory(); checkInputPatternParameter(); } /** * use by gradle */ public Configuration(InputParam param) throws IOException, TinkerPatchException { mUsingGradle = true; mSoFilePattern = new HashSet<>(); mDexFilePattern = new HashSet<>(); mDexLoaderPattern = new HashSet<>(); mDexIgnoreWarningLoaderPattern = new HashSet<>(); mResFilePattern = new HashSet<>(); mResRawPattern = new HashSet<>(); mResIgnoreChangePattern = new HashSet<>(); mResIgnoreChangeWarningPattern = new HashSet<>(); mPackageFields = new HashMap<>(); for (String item : param.soFilePattern) { addToPatterns(item, mSoFilePattern); } for (String item : param.dexFilePattern) { addToPatterns(item, mDexFilePattern); } for (String item : param.resourceFilePattern) { mResRawPattern.add(item); addToPatterns(item, mResFilePattern); } for (String item : param.resourceIgnoreChangePattern) { addToPatterns(item, mResIgnoreChangePattern); } for (String item : param.resourceIgnoreChangeWarningPattern) { addToPatterns(item, mResIgnoreChangeWarningPattern); } mLargeModSize = param.largeModSize; //only gradle have the param mUseApplyResource = param.useApplyResource; mDexLoaderPattern.addAll(param.dexLoaderPattern); mDexIgnoreWarningLoaderPattern.addAll(param.dexIgnoreWarningLoaderPattern); //can be only raw or jar if (param.dexMode.equals("raw")) { mDexRaw = true; } mOldApkPath = param.oldApk; mOldApkFile = new File(mOldApkPath); mNewApkPath = param.newApk; mNewApkFile = new File(mNewApkPath); mOutFolder = param.outFolder; mIgnoreWarning = param.ignoreWarning; mAllowLoaderInAnyDex = param.allowLoaderInAnyDex; mRemoveLoaderForAllDex= param.removeLoaderForAllDex; mIsProtectedApp = param.isProtectedApp; mSupportHotplugComponent = param.supportHotplugComponent; mSevenZipPath = param.sevenZipPath; mPackageFields = param.configFields; mUseSignAPk = param.useSign; mCustomDiffPath = param.customDiffPath; mCustomDiffPathArgs = param.customDiffPathArgs; setSignData(param.signFile, param.keypass, param.storealias, param.storepass); FileOperation.cleanDir(new File(mOutFolder)); createTempDirectory(); checkInputPatternParameter(); mArkHotPatchName = param.arkHotPatchName; mArkHotPatchPath = param.arkHotPatchPath; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("configuration: \n"); sb.append("oldApk:" + mOldApkPath + "\n"); sb.append("newApk:" + mNewApkPath + "\n"); sb.append("outputFolder:" + mOutFolder + "\n"); sb.append("isIgnoreWarning:" + mIgnoreWarning + "\n"); sb.append("isAllowLoaderClassInAnyDex:" + mAllowLoaderInAnyDex + "\n"); sb.append("isRemoveLoaderForAllDex:" + mRemoveLoaderForAllDex + "\n"); sb.append("isProtectedApp:" + mIsProtectedApp + "\n"); sb.append("7-ZipPath:" + mSevenZipPath + "\n"); sb.append("useSignAPk:" + mUseSignAPk + "\n"); sb.append("package meta fields: \n"); for (String name : mPackageFields.keySet()) { sb.append("filed name:" + name + ", filed value:" + mPackageFields.get(name) + "\n"); } sb.append("dex configs: \n"); if (mDexRaw) { sb.append("dexMode: raw" + "\n"); } else { sb.append("dexMode: jar" + "\n"); } for (Pattern name : mDexFilePattern) { sb.append("dexPattern:" + name.toString() + "\n"); } for (String name : mDexLoaderPattern) { sb.append("dex loader:" + name + "\n"); } for (String name : mDexIgnoreWarningLoaderPattern) { sb.append("dex ignore warning loader:" + name.toString() + "\n"); } sb.append("lib configs: \n"); for (Pattern name : mSoFilePattern) { sb.append("libPattern:" + name.toString() + "\n"); } sb.append("resource configs: \n"); for (Pattern name : mResFilePattern) { sb.append("resPattern:" + name.toString() + "\n"); } for (Pattern name : mResIgnoreChangePattern) { sb.append("resIgnore change:" + name.toString() + "\n"); } for (Pattern name : mResIgnoreChangeWarningPattern) { sb.append("resIgnore change warning:" + name.toString() + "\n"); } sb.append("largeModSize:" + mLargeModSize + "kb\n"); sb.append("useApplyResource:" + mUseApplyResource + "\n"); sb.append("ArkHot: " + mArkHotPatchPath + " / " + mArkHotPatchName + "\n"); return sb.toString(); } private void createTempDirectory() throws TinkerPatchException { mTempResultDir = new File(mOutFolder + File.separator + TypedValue.PATH_PATCH_FILES); FileOperation.deleteDir(mTempResultDir); if (!mTempResultDir.exists()) { mTempResultDir.mkdir(); } String oldApkName = mOldApkFile.getName(); if (!oldApkName.endsWith(TypedValue.FILE_APK)) { throw new TinkerPatchException( String.format("input apk file path must end with .apk, yours %s\n", oldApkName) ); } String newApkName = mNewApkFile.getName(); if (!newApkName.endsWith(TypedValue.FILE_APK)) { throw new TinkerPatchException( String.format("input apk file path must end with .apk, yours %s\n", newApkName) ); } String tempOldName = oldApkName.substring(0, oldApkName.indexOf(TypedValue.FILE_APK)); String tempNewName = newApkName.substring(0, newApkName.indexOf(TypedValue.FILE_APK)); // Bugfix: For windows user, filename is case-insensitive. if (tempNewName.equalsIgnoreCase(tempOldName)) { tempOldName += "-old"; tempNewName += "-new"; } mTempUnzipOldDir = new File(mOutFolder, tempOldName); mTempUnzipNewDir = new File(mOutFolder, tempNewName); } public void setSignData(File signatureFile, String keypass, String storealias, String storepass) throws IOException { if (mUseSignAPk) { mSignatureFile = signatureFile; if (!mSignatureFile.exists()) { throw new IOException( String.format("the signature file do not exit, raw path= %s\n", mSignatureFile.getAbsolutePath()) ); } mKeyPass = keypass; mStoreAlias = storealias; mStorePass = storepass; } } private void checkInputPatternParameter() throws TinkerPatchException { if (mSoFilePattern.isEmpty() && mDexFilePattern.isEmpty() && mResFilePattern.isEmpty()) { throw new TinkerPatchException("no dex, so or resource pattern are found"); } if (mLargeModSize <= 0) { throw new TinkerPatchException("largeModSize must be larger than 0"); } } /** * read args from xml **/ void readXmlConfig(File xmlConfigFile) throws IOException, ParserConfigurationException, SAXException { if (!xmlConfigFile.exists()) { return; } System.out.printf("reading config file, %s\n", xmlConfigFile.getAbsolutePath()); BufferedInputStream input = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); input = new BufferedInputStream(new FileInputStream(xmlConfigFile)); InputSource source = new InputSource(input); factory.setNamespaceAware(false); factory.setValidating(false); DocumentBuilder builder = factory.newDocumentBuilder(); // Block any external content resolving actions since we don't need them and a report // says these actions may cause security problems. builder.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(); } }); Document document = builder.parse(source); NodeList issues = document.getElementsByTagName(TAG_ISSUE); for (int i = 0, count = issues.getLength(); i < count; i++) { Node node = issues.item(i); Element element = (Element) node; String id = element.getAttribute(ATTR_ID); if (id.length() == 0) { System.err.println("Invalid config file: Missing required issue id attribute"); continue; } if (id.equals(PROPERTY_ISSUE)) { readPropertyFromXml(node); } else if (id.equals(DEX_ISSUE)) { readDexPatternsFromXml(node); } else if (id.equals(SO_ISSUE)) { readLibPatternsFromXml(node); } else if (id.equals(RES_ISSUE)) { readResPatternsFromXml(node); } else if (id.equals(PACKAGE_CONFIG_ISSUE)) { readPackageConfigFromXml(node); } else if (id.equals(SIGN_ISSUE)) { if (mUseSignAPk) { readSignFromXml(node); } } else if (id.equals(ARKHOT_ISSUE)) { readArkHotPropertyFromXml(node); } else { System.err.println("unknown issue " + id); } } } finally { IOHelper.closeQuietly(input); } } private void readPropertyFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (value == null) { value = ""; } if (tagName.equals(ATTR_IGNORE_WARNING)) { mIgnoreWarning = value.equals("true"); } else if (tagName.equals(ATTR_ALLOW_LOADER_IN_ANY_DEX)) { mAllowLoaderInAnyDex = value.equals("true"); } else if (tagName.equals(ATTR_REMOVE_LOADER_FOR_ALL_DEX)) { mRemoveLoaderForAllDex = value.equals("true"); } else if (tagName.equals(ATTR_IS_PROTECTED_APP)) { mIsProtectedApp = value.equals("true"); } else if (tagName.equals(ATTR_SUPPORT_HOTPLUG_COMPONENT)) { mSupportHotplugComponent = value.equals("true"); } else if (tagName.equals(ATTR_USE_SIGN)) { mUseSignAPk = value.equals("true"); } else if (tagName.equals(ATTR_SEVEN_ZIP_PATH)) { File sevenZipFile = new File(value); if (sevenZipFile.exists()) { mSevenZipPath = value; } else { mSevenZipPath = "7za"; } } else if (tagName.equals(ATTR_CUSTOM_DIFF_PATH)) { mCustomDiffPath = value; } else if (tagName.equals(ATTR_CUSTOM_DIFF_PATH_ARGS)) { mCustomDiffPathArgs = value; } else { System.err.println("unknown property tag " + tagName); } } } } } private void readArkHotPropertyFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (tagName.equals(ATTR_ARKHOT_PATH)) { mArkHotPatchPath = value; mArkHotPatchPath.trim(); } else if (tagName.equals(ATTR_ARKHOT_NAME)) { mArkHotPatchName = value; mArkHotPatchName.trim(); } else { System.err.println("unknown dex tag " + tagName); } } } } } private void readSignFromXml(Node node) throws IOException { if (mSignatureFile != null) { System.err.println("already set the sign info from command line, ignore this"); return; } NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (value.length() == 0) { throw new IOException( String.format("Invalid config file: Tag:%s Missing required attribute %s\n",tagName, ATTR_VALUE) ); } if (tagName.equals(ATTR_SIGN_FILE_PATH)) { mSignatureFile = new File(value); if (!mSignatureFile.exists()) { throw new IOException( String.format("the signature file do not exit, raw path= %s\n", mSignatureFile.getAbsolutePath()) ); } } else if (tagName.equals(ATTR_SIGN_FILE_STOREPASS)) { mStorePass = value; mStorePass = mStorePass.trim(); } else if (tagName.equals(ATTR_SIGN_FILE_KEYPASS)) { mKeyPass = value; mKeyPass = mKeyPass.trim(); } else if (tagName.equals(ATTR_SIGN_FILE_ALIAS)) { mStoreAlias = value; mStoreAlias = mStoreAlias.trim(); } else { System.err.println("unknown sign tag " + tagName); } } } } } private void readDexPatternsFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (tagName.equals(ATTR_DEX_MODE)) { if (value.equals("raw")) { mDexRaw = true; } } else if (tagName.equals(ATTR_PATTERN)) { addToPatterns(value, mDexFilePattern); } else if (tagName.equals(ATTR_LOADER)) { mDexLoaderPattern.add(value); } else if (tagName.equals(ATTR_IGNORE_CHANGE)) { mDexIgnoreWarningLoaderPattern.add(value); } else { System.err.println("unknown dex tag " + tagName); } } } } } private void readLibPatternsFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (tagName.equals(ATTR_PATTERN)) { addToPatterns(value, mSoFilePattern); } else { System.err.println("unknown dex tag " + tagName); } } } } } private void readResPatternsFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); if (tagName.equals(ATTR_PATTERN)) { mResRawPattern.add(value); addToPatterns(value, mResFilePattern); } else if (tagName.equals(ATTR_IGNORE_CHANGE)) { if (!Utils.isBlank(value)) { addToPatterns(value, mResIgnoreChangePattern); } } else if (tagName.equals(ATTR_IGNORE_CHANGE_WARNING)) { if (!Utils.isBlank(value)) { addToPatterns(value, mResIgnoreChangeWarningPattern); } } else if (tagName.equals(ATTR_RES_LARGE_MOD)) { mLargeModSize = Integer.valueOf(value); } else { System.err.println("unknown dex tag " + tagName); } } } } } private void readPackageConfigFromXml(Node node) throws IOException { NodeList childNodes = node.getChildNodes(); if (childNodes.getLength() > 0) { for (int j = 0, n = childNodes.getLength(); j < n; j++) { Node child = childNodes.item(j); if (child.getNodeType() == Node.ELEMENT_NODE) { Element check = (Element) child; String tagName = check.getTagName(); String value = check.getAttribute(ATTR_VALUE); String name = check.getAttribute(ATTR_NAME); if (tagName.equals(ATTR_CONFIG_FIELD)) { mPackageFields.put(name, value); } else { System.err.println("unknown package config tag " + tagName); } } } } } private void addToPatterns(String value, HashSet patterns) throws IOException { if (value.length() == 0) { throw new IOException( String.format("Invalid config file: Missing required attribute %s\n", ATTR_VALUE) ); } value = Utils.convertToPatternString(value); Pattern pattern = Pattern.compile(value); patterns.add(pattern); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/InputParam.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.patch; import java.io.File; import java.util.ArrayList; import java.util.HashMap; /** * Created by zhangshaowen on 1/9/16. */ public class InputParam { /** * tinkerPatch */ public final String oldApk; public final String newApk; public final String outFolder; public final File signFile; public final String keypass; public final String storealias; public final String storepass; public final String customDiffPath; public final String customDiffPathArgs; public final boolean ignoreWarning; public final boolean allowLoaderInAnyDex; public final boolean removeLoaderForAllDex; public final boolean isProtectedApp; public final boolean supportHotplugComponent; public final boolean useSign; /** * tinkerPatch.dex */ public final ArrayList dexFilePattern; public final ArrayList dexLoaderPattern; public final ArrayList dexIgnoreWarningLoaderPattern; public final String dexMode; /** * tinkerPatch.lib */ public final ArrayList soFilePattern; /** * tinkerPatch.resource pattern */ public final ArrayList resourceFilePattern; /** * tinkerPath.resource ignoreChange */ public final ArrayList resourceIgnoreChangePattern; /** * tinkerPatch.resource ignoreChangeWarning */ public final ArrayList resourceIgnoreChangeWarningPattern; /** * tinkerPath.resource largeModSize */ public final int largeModSize; /** * tinkerPath.buildConfig applyResourceMapping */ public final boolean useApplyResource; /** * tinkerPatch.packageConfig */ public final HashMap configFields; /** * tinkerPatch.sevenZip */ public final String sevenZipPath; /** * TinkerPatch ark */ public final String arkHotPatchPath; public final String arkHotPatchName; private InputParam( String oldApk, String newApk, String outFolder, File signFile, String keypass, String storealias, String storepass, String customDiffPath, String customDiffPathArgs, boolean ignoreWarning, boolean allowLoaderInAnyDex, boolean removeLoaderForAllDex, boolean isProtectedApp, boolean supportHotplugComponent, boolean useSign, ArrayList dexFilePattern, ArrayList dexLoaderPattern, ArrayList dexIgnoreChangeLoaderPattern, String dexMode, ArrayList soFilePattern, ArrayList resourceFilePattern, ArrayList resourceIgnoreChangePattern, ArrayList resourceIgnoreChangeWarningPattern, int largeModSize, boolean useApplyResource, HashMap configFields, String sevenZipPath, String arkHotPatchPath, String arkHotPatchName ) { this.oldApk = oldApk; this.newApk = newApk; this.outFolder = outFolder; this.signFile = signFile; this.keypass = keypass; this.storealias = storealias; this.storepass = storepass; this.customDiffPath = customDiffPath; this.customDiffPathArgs = customDiffPathArgs; this.ignoreWarning = ignoreWarning; this.allowLoaderInAnyDex = allowLoaderInAnyDex; this.removeLoaderForAllDex = removeLoaderForAllDex; this.isProtectedApp = isProtectedApp; this.supportHotplugComponent = supportHotplugComponent; this.useSign = useSign; this.dexFilePattern = dexFilePattern; this.dexLoaderPattern = dexLoaderPattern; this.dexIgnoreWarningLoaderPattern = dexIgnoreChangeLoaderPattern; this.dexMode = dexMode; this.soFilePattern = soFilePattern; this.resourceFilePattern = resourceFilePattern; this.resourceIgnoreChangePattern = resourceIgnoreChangePattern; this.resourceIgnoreChangeWarningPattern = resourceIgnoreChangeWarningPattern; this.largeModSize = largeModSize; this.useApplyResource = useApplyResource; this.configFields = configFields; this.sevenZipPath = sevenZipPath; this.arkHotPatchPath = arkHotPatchPath; this.arkHotPatchName = arkHotPatchName; } public static class Builder { /** * tinkerPatch */ private String oldApk; private String newApk; private String outFolder; private File signFile; private String keypass; private String storealias; private String storepass; private String customDiffPath; private String customDiffPathArgs; private boolean ignoreWarning; private boolean allowLoaderInAnyDex; private boolean removeLoaderForAllDex; private boolean isProtectedApp; private boolean isComponentHotplugSupported; private boolean useSign; /** * tinkerPatch.dex */ private ArrayList dexFilePattern; private ArrayList dexLoaderPattern; private ArrayList dexIgnoreWarningLoaderPattern; private String dexMode; /** * tinkerPatch.lib */ private ArrayList soFilePattern; /** * tinkerPath.resource pattern */ private ArrayList resourceFilePattern; /** * tinkerPath.resource ignoreChange */ private ArrayList resourceIgnoreChangePattern; /** * tinkerPatch.resource ignoreChangeWarning */ private ArrayList resourceIgnoreChangeWarningPattern; /** * tinkerPath.resource largeModSize */ private int largeModSize; /** * tinkerPath.buildConfig applyResourceMapping */ private boolean useApplyResource; /** * tinkerPatch.packageConfig */ private HashMap configFields; /** * tinkerPatch.sevenZip */ private String sevenZipPath; /** * tinkerPatch ark */ private String arkHotPatchPath; private String arkHotPatchName; public Builder() { } public Builder setOldApk(String oldApk) { this.oldApk = oldApk; return this; } public Builder setNewApk(String newApk) { this.newApk = newApk; return this; } public Builder setSoFilePattern(ArrayList soFilePattern) { this.soFilePattern = soFilePattern; return this; } public Builder setResourceFilePattern(ArrayList resourceFilePattern) { this.resourceFilePattern = resourceFilePattern; return this; } public Builder setResourceIgnoreChangePattern(ArrayList resourceIgnoreChangePattern) { this.resourceIgnoreChangePattern = resourceIgnoreChangePattern; return this; } public Builder setResourceIgnoreChangeWarningPattern(ArrayList resourceIgnoreChangeWarningPattern) { this.resourceIgnoreChangeWarningPattern = resourceIgnoreChangeWarningPattern; return this; } public Builder setResourceLargeModSize(int largeModSize) { this.largeModSize = largeModSize; return this; } public Builder setUseApplyResource(boolean useApplyResource) { this.useApplyResource = useApplyResource; return this; } public Builder setDexFilePattern(ArrayList dexFilePattern) { this.dexFilePattern = dexFilePattern; return this; } public Builder setOutBuilder(String outFolder) { this.outFolder = outFolder; return this; } public Builder setSignFile(File signFile) { this.signFile = signFile; return this; } public Builder setKeypass(String keypass) { this.keypass = keypass; return this; } public Builder setStorealias(String storealias) { this.storealias = storealias; return this; } public Builder setStorepass(String storepass) { this.storepass = storepass; return this; } public Builder setIgnoreWarning(boolean ignoreWarning) { this.ignoreWarning = ignoreWarning; return this; } public Builder setAllowLoaderInAnyDex(boolean allowLoaderInAnyDex) { this.allowLoaderInAnyDex = allowLoaderInAnyDex; return this; } public Builder setCustomDiffPath(String path) { this.customDiffPath = path; return this; } public Builder setCustomDiffPathArgs(String args) { this.customDiffPathArgs = args; return this; } public Builder setRemoveLoaderForAllDex(boolean removeLoaderForAllDex){ this.removeLoaderForAllDex = removeLoaderForAllDex; return this; } public Builder setIsProtectedApp(boolean isProtectedApp) { this.isProtectedApp = isProtectedApp; return this; } public Builder setIsComponentHotplugSupported(boolean isComponentHotplugSupported) { this.isComponentHotplugSupported = isComponentHotplugSupported; return this; } public Builder setDexLoaderPattern(ArrayList dexLoaderPattern) { this.dexLoaderPattern = dexLoaderPattern; return this; } public Builder setDexIgnoreWarningLoaderPattern(ArrayList loader) { this.dexIgnoreWarningLoaderPattern = loader; return this; } public Builder setDexMode(String dexMode) { this.dexMode = dexMode; return this; } public Builder setConfigFields(HashMap configFields) { this.configFields = configFields; return this; } public Builder setSevenZipPath(String sevenZipPath) { this.sevenZipPath = sevenZipPath; return this; } public Builder setUseSign(boolean useSign) { this.useSign = useSign; return this; } public Builder setArkHotPath(String path) { this.arkHotPatchPath = path; return this; } public Builder setArkHotName(String name) { this.arkHotPatchName = name; return this; } public InputParam create() { return new InputParam( oldApk, newApk, outFolder, signFile, keypass, storealias, storepass, customDiffPath, customDiffPathArgs, ignoreWarning, allowLoaderInAnyDex, removeLoaderForAllDex, isProtectedApp, isComponentHotplugSupported, useSign, dexFilePattern, dexLoaderPattern, dexIgnoreWarningLoaderPattern, dexMode, soFilePattern, resourceFilePattern, resourceIgnoreChangePattern, resourceIgnoreChangeWarningPattern, largeModSize, useApplyResource, configFields, sevenZipPath, arkHotPatchPath, arkHotPatchName ); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/patch/Runner.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.patch; import com.tencent.tinker.build.builder.PatchBuilder; import com.tencent.tinker.build.decoder.ApkDecoder; import com.tencent.tinker.build.info.PatchInfo; import com.tencent.tinker.build.util.Logger; import com.tencent.tinker.build.util.TinkerPatchException; import java.io.IOException; /** * Created by zhangshaowen on 2/26/16. */ public class Runner { public static final int ERRNO_ERRORS = 1; public static final int ERRNO_USAGE = 2; private final boolean mIsGradleEnv; protected static long mBeginTime; protected Configuration mConfig; public Runner(boolean isGradleEnv) { mIsGradleEnv = isGradleEnv; } public static void gradleRun(InputParam inputParam) { mBeginTime = System.currentTimeMillis(); Runner m = new Runner(true); m.run(inputParam); } private void run(InputParam inputParam) { loadConfigFromGradle(inputParam); try { Logger.initLogger(mConfig); tinkerPatch(); } catch (IOException e) { goToError(e, ERRNO_ERRORS); } finally { Logger.closeLogger(); } } protected void tinkerPatch() { Logger.d("-----------------------Tinker patch begin-----------------------"); Logger.d(mConfig.toString()); try { //gen patch ApkDecoder decoder = new ApkDecoder(mConfig); decoder.onAllPatchesStart(); decoder.patch(mConfig.mOldApkFile, mConfig.mNewApkFile); decoder.onAllPatchesEnd(); //gen meta file and version file PatchInfo info = new PatchInfo(mConfig); info.gen(); //build patch PatchBuilder builder = new PatchBuilder(mConfig); builder.buildPatch(); } catch (Throwable e) { goToError(e, ERRNO_USAGE); } Logger.d("Tinker patch done, total time cost: %fs", diffTimeFromBegin()); Logger.d("Tinker patch done, you can go to file to find the output %s", mConfig.mOutFolder); Logger.d("-----------------------Tinker patch end-------------------------"); } private void loadConfigFromGradle(InputParam inputParam) { try { mConfig = new Configuration(inputParam); } catch (IOException e) { e.printStackTrace(); } catch (TinkerPatchException e) { e.printStackTrace(); } } public void goToError(Throwable thr, int errCode) { if (mIsGradleEnv) { throw new RuntimeException(thr); } else { thr.printStackTrace(System.err); System.exit(errCode); } } public double diffTimeFromBegin() { long end = System.currentTimeMillis(); return (end - mBeginTime) / 1000.0; } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/CustomDiff.java ================================================ package com.tencent.tinker.build.util; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CustomDiff { public static boolean checkHasCustomDiff(Configuration config) { return config.mCustomDiffPath != null && !config.mCustomDiffPath.trim().isEmpty() && config.mCustomDiffPathArgs != null && !config.mCustomDiffPathArgs.isEmpty(); } public static void diffFile(String mCustomDiffPath,String mCustomDiffPathArgs, File oldFile, File newFile, File diffFile) throws IOException { String outPath = diffFile.getAbsolutePath(); String cmd = mCustomDiffPath; List cmds = new ArrayList<>(); for (String s : cmd.split(" ")) { if (!s.isEmpty()) { cmds.add(s); } } for (String s : mCustomDiffPathArgs.split(" ")) { if (!s.isEmpty()) { cmds.add(s); } } cmds.add(oldFile.getAbsolutePath()); cmds.add(newFile.getAbsolutePath()); cmds.add(outPath); System.out.println(cmd); for (String s : cmds) { System.out.print(s + " "); } System.out.println(); ProcessBuilder pb = new ProcessBuilder(cmds); pb.redirectErrorStream(true); pb.inheritIO(); Process pro = null; LineNumberReader reader = null; try { boolean isWindows = System.getProperty("os.name") .toLowerCase().startsWith("windows"); ProcessBuilder builder; if (isWindows) { cmds.add(0, "cmd.exe"); builder = new ProcessBuilder(cmds); } else { builder = new ProcessBuilder(cmds); } Process process = builder.start(); BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = br.readLine()) != null) { Logger.d(line); } int exitCode = process.waitFor(); Logger.d( "run init script done, exitCode: %d", exitCode); process.destroy(); } catch (IOException e) { FileOperation.deleteFile(diffFile); Logger.e("CustomDecoder error" + e.getMessage()); } catch (InterruptedException e) { Logger.e("CustomDecoder error" + e.getMessage()); } finally { //destroy the stream try { pro.waitFor(); } catch (Throwable ignored) { // Ignored. } try { pro.destroy(); } catch (Throwable ignored) { // Ignored. } IOHelper.closeQuietly(reader); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/DexClassesComparator.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.android.dex.Annotation; import com.tencent.tinker.android.dex.AnnotationSet; import com.tencent.tinker.android.dex.AnnotationSetRefList; import com.tencent.tinker.android.dex.AnnotationsDirectory; import com.tencent.tinker.android.dex.CallSiteId; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.ClassData.Field; import com.tencent.tinker.android.dex.ClassData.Method; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.DebugInfoItem; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.EncodedValue; import com.tencent.tinker.android.dex.EncodedValueReader; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.MethodHandle; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dex.TableOfContents; import com.tencent.tinker.android.dex.TypeList; import com.tencent.tinker.android.dex.io.DexDataBuffer; import com.tencent.tinker.android.dx.instruction.InstructionComparator; import com.tencent.tinker.build.dexpatcher.util.PatternUtils; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger.IDexPatcherLogger; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; /** * Created by tangyinsheng on 2016/4/14. */ public final class DexClassesComparator { private static final String TAG = "DexClassesComparator"; public static final int COMPARE_MODE_NORMAL = 0; public static final int COMPARE_MODE_REFERRER_AFFECTED_CHANGE_ONLY = 1; private static final int DBG_FIRST_SPECIAL = 0x0A; // the smallest special opcode private static final int DBG_LINE_BASE = -4; // the smallest line number increment private static final int DBG_LINE_RANGE = 15; // the number of line increments represented private int compareMode = COMPARE_MODE_NORMAL; private final List addedClassInfoList = new ArrayList<>(); private final List deletedClassInfoList = new ArrayList<>(); // classDesc => [oldClassInfo, newClassInfo] private final Map changedClassDescToClassInfosMap = new HashMap<>(); private final Set patternsOfClassDescToCheck = new HashSet<>(); private final Set patternsOfIgnoredRemovedClassDesc = new HashSet<>(); private final Set oldDescriptorOfClassesToCheck = new HashSet<>(); private final Set newDescriptorOfClassesToCheck = new HashSet<>(); private final Map oldClassDescriptorToClassInfoMap = new HashMap<>(); private final Map newClassDescriptorToClassInfoMap = new HashMap<>(); // Record class descriptors whose references key (index or offset) of methods and fields // are changed. private final Set refAffectedClassDescs = new HashSet<>(); private final DexPatcherLogger logger = new DexPatcherLogger(); public DexClassesComparator(String patternStringOfClassDescToCheck) { patternsOfClassDescToCheck.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStringOfClassDescToCheck) ) ); } public DexClassesComparator(String... patternStringsOfClassDescToCheck) { for (String patternStr : patternStringsOfClassDescToCheck) { patternsOfClassDescToCheck.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } } public DexClassesComparator(Collection patternStringsOfClassDescToCheck) { for (String patternStr : patternStringsOfClassDescToCheck) { patternsOfClassDescToCheck.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } } public void setIgnoredRemovedClassDescPattern(String... patternStringsOfLoaderClassDesc) { patternsOfIgnoredRemovedClassDesc.clear(); for (String patternStr : patternStringsOfLoaderClassDesc) { patternsOfIgnoredRemovedClassDesc.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } } public void setIgnoredRemovedClassDescPattern(Collection patternStringsOfLoaderClassDesc) { patternsOfIgnoredRemovedClassDesc.clear(); for (String patternStr : patternStringsOfLoaderClassDesc) { patternsOfIgnoredRemovedClassDesc.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } } public void setCompareMode(int mode) { if (mode == COMPARE_MODE_NORMAL || mode == COMPARE_MODE_REFERRER_AFFECTED_CHANGE_ONLY) { this.compareMode = mode; } else { throw new IllegalArgumentException("bad compare mode: " + mode); } } public void setLogger(IDexPatcherLogger logger) { this.logger.setLoggerImpl(logger); } public List getAddedClassInfos() { return Collections.unmodifiableList(addedClassInfoList); } public List getDeletedClassInfos() { return Collections.unmodifiableList(deletedClassInfoList); } public Map getChangedClassDescToInfosMap() { return Collections.unmodifiableMap(changedClassDescToClassInfosMap); } public void startCheck(File oldDexFile, File newDexFile) throws IOException { startCheck(new Dex(oldDexFile), new Dex(newDexFile)); } public void startCheck(Dex oldDex, Dex newDex) { startCheck(DexGroup.wrap(oldDex), DexGroup.wrap(newDex)); } public void startCheck(DexGroup oldDexGroup, DexGroup newDexGroup) { // Init assist structures. addedClassInfoList.clear(); deletedClassInfoList.clear(); changedClassDescToClassInfosMap.clear(); oldDescriptorOfClassesToCheck.clear(); newDescriptorOfClassesToCheck.clear(); oldClassDescriptorToClassInfoMap.clear(); newClassDescriptorToClassInfoMap.clear(); refAffectedClassDescs.clear(); // Map classDesc and typeIndex to classInfo // and collect typeIndex of classes to check in oldDexes. for (Dex oldDex : oldDexGroup.dexes) { int classDefIndex = 0; for (ClassDef oldClassDef : oldDex.classDefs()) { String desc = oldDex.typeNames().get(oldClassDef.typeIndex); if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { if (!oldDescriptorOfClassesToCheck.add(desc)) { throw new IllegalStateException( String.format( "duplicate class descriptor [%s] in different old dexes.", desc ) ); } } DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, oldClassDef, oldDex); ++classDefIndex; oldClassDescriptorToClassInfoMap.put(desc, classInfo); } } // Map classDesc and typeIndex to classInfo // and collect typeIndex of classes to check in newDexes. for (Dex newDex : newDexGroup.dexes) { int classDefIndex = 0; for (ClassDef newClassDef : newDex.classDefs()) { String desc = newDex.typeNames().get(newClassDef.typeIndex); if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { if (!newDescriptorOfClassesToCheck.add(desc)) { throw new IllegalStateException( String.format( "duplicate class descriptor [%s] in different new dexes.", desc ) ); } } DexClassInfo classInfo = new DexClassInfo(desc, classDefIndex, newClassDef, newDex); ++classDefIndex; newClassDescriptorToClassInfoMap.put(desc, classInfo); } } Set deletedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck); deletedClassDescs.removeAll(newDescriptorOfClassesToCheck); for (String desc : deletedClassDescs) { // These classes are deleted as we expect to, so we remove them // from result. if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) { logger.i(TAG, "Ignored deleted class: %s", desc); } else { logger.i(TAG, "Deleted class: %s", desc); deletedClassInfoList.add(oldClassDescriptorToClassInfoMap.get(desc)); } } Set addedClassDescs = new HashSet<>(newDescriptorOfClassesToCheck); addedClassDescs.removeAll(oldDescriptorOfClassesToCheck); for (String desc : addedClassDescs) { if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) { logger.i(TAG, "Ignored added class: %s", desc); } else { logger.i(TAG, "Added class: %s", desc); addedClassInfoList.add(newClassDescriptorToClassInfoMap.get(desc)); } } Set mayBeChangedClassDescs = new HashSet<>(oldDescriptorOfClassesToCheck); mayBeChangedClassDescs.retainAll(newDescriptorOfClassesToCheck); for (String desc : mayBeChangedClassDescs) { DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(desc); DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(desc); switch (compareMode) { case COMPARE_MODE_NORMAL: { if (!isSameClass( oldClassInfo.owner, newClassInfo.owner, oldClassInfo.classDef, newClassInfo.classDef )) { if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) { logger.i(TAG, "Ignored changed class: %s", desc); } else { logger.i(TAG, "Changed class: %s", desc); changedClassDescToClassInfosMap.put( desc, new DexClassInfo[]{oldClassInfo, newClassInfo} ); } } break; } case COMPARE_MODE_REFERRER_AFFECTED_CHANGE_ONLY: { if (isClassChangeAffectedToReferrer( oldClassInfo.owner, newClassInfo.owner, oldClassInfo.classDef, newClassInfo.classDef )) { if (Utils.isStringMatchesPatterns(desc, patternsOfIgnoredRemovedClassDesc)) { logger.i(TAG, "Ignored referrer-affected changed class: %s", desc); } else { logger.i(TAG, "Referrer-affected change class: %s", desc); changedClassDescToClassInfosMap.put( desc, new DexClassInfo[]{oldClassInfo, newClassInfo} ); } } break; } default: { break; } } } } private boolean isClassChangeAffectedToReferrer( Dex oldDex, Dex newDex, ClassDef oldClassDef, ClassDef newClassDef ) { boolean result = false; String classDesc = oldDex.typeNames().get(oldClassDef.typeIndex); do { if (refAffectedClassDescs.contains(classDesc)) { result = true; return result; } // Any changes on superclass could affect refs of members in current class. if (isTypeChangeAffectedToReferrer( oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex )) { result = true; break; } // Any changes on current class's interface list could affect refs // of members in current class. short[] oldInterfaceTypeIds = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef); short[] newInterfaceTypeIds = newDex.interfaceTypeIndicesFromClassDef(newClassDef); if (isTypeIdsChangeAffectedToReferrer( oldDex, newDex, oldInterfaceTypeIds, newInterfaceTypeIds, false )) { result = true; break; } // Any changes on current class's member lists could affect refs // of members in current class. ClassData oldClassData = (oldClassDef.classDataOffset != 0 ? oldDex.readClassData(oldClassDef) : null); ClassData newClassData = (newClassDef.classDataOffset != 0 ? newDex.readClassData(newClassDef) : null); if (isClassDataChangeAffectedToReferrer( oldDex, newDex, oldClassData, newClassData )) { result = true; break; } } while (false); if (result) { refAffectedClassDescs.add(classDesc); } return result; } private boolean isTypeChangeAffectedToReferrer( Dex oldDex, Dex newDex, int oldTypeId, int newTypeId ) { if (oldTypeId != ClassDef.NO_INDEX && newTypeId != ClassDef.NO_INDEX) { String oldClassDesc = oldDex.typeNames().get(oldTypeId & 0xFFFF); String newClassDesc = newDex.typeNames().get(newTypeId & 0xFFFF); if (!oldClassDesc.equals(newClassDesc)) { return true; } final DexClassInfo oldClassInfo = oldClassDescriptorToClassInfoMap.get(oldClassDesc); final DexClassInfo newClassInfo = newClassDescriptorToClassInfoMap.get(newClassDesc); ClassDef oldClassDef = (oldClassInfo != null ? oldClassInfo.classDef : null); ClassDef newClassDef = (newClassInfo != null ? newClassInfo.classDef : null); if (oldClassDef != null && newClassDef != null) { return isClassChangeAffectedToReferrer(oldClassInfo.owner, newClassInfo.owner, oldClassDef, newClassDef); } else if (oldClassDef == null && newClassDef == null) { return false; } else { // If current comparing class is ignored, since it must be removed // in patched dexes as we expected, here we ignore this kind of changes. return !Utils.isStringMatchesPatterns(oldClassDesc, patternsOfIgnoredRemovedClassDesc); } } else { return !(oldTypeId == ClassDef.NO_INDEX && newTypeId == ClassDef.NO_INDEX); } } private boolean isTypeIdsChangeAffectedToReferrer( Dex oldDex, Dex newDex, short[] oldTypeIds, short[] newTypeIds, boolean compareNameOnly ) { if (oldTypeIds.length != newTypeIds.length) { return true; } int typeIdCount = oldTypeIds.length; for (int i = 0; i < typeIdCount; ++i) { if (compareNameOnly) { final int oldTypeId = oldTypeIds[i] != ClassDef.NO_INDEX ? oldTypeIds[i] & 0xFFFF : oldTypeIds[i]; final int newTypeId = newTypeIds[i] != ClassDef.NO_INDEX ? newTypeIds[i] & 0xFFFF : newTypeIds[i]; if (oldTypeId != ClassDef.NO_INDEX && newTypeId != ClassDef.NO_INDEX) { String oldTypeName = oldDex.typeNames().get(oldTypeId); String newTypeName = newDex.typeNames().get(newTypeId); if (!oldTypeName.equals(newTypeName)) { return true; } } else if (oldTypeId != ClassDef.NO_INDEX || newTypeId != ClassDef.NO_INDEX) { return true; } } else { if (isTypeChangeAffectedToReferrer(oldDex, newDex, oldTypeIds[i], newTypeIds[i])) { return true; } } } return false; } private boolean isClassDataChangeAffectedToReferrer( Dex oldDex, Dex newDex, ClassData oldClassData, ClassData newClassData ) { if (oldClassData != null && newClassData != null) { if (isFieldsChangeAffectedToReferrer( oldDex, newDex, oldClassData.instanceFields, newClassData.instanceFields )) { return true; } if (isFieldsChangeAffectedToReferrer( oldDex, newDex, oldClassData.staticFields, newClassData.staticFields )) { return true; } if (isMethodsChangeAffectedToReferrer( oldDex, newDex, oldClassData.directMethods, newClassData.directMethods )) { return true; } if (isMethodsChangeAffectedToReferrer( oldDex, newDex, oldClassData.virtualMethods, newClassData.virtualMethods )) { return true; } } else { if (!(oldClassData == null && newClassData == null)) { return true; } } return false; } private boolean isFieldsChangeAffectedToReferrer( Dex oldDex, Dex newDex, Field[] oldFields, Field[] newFields ) { if (oldFields.length != newFields.length) { return true; } int fieldCount = oldFields.length; for (int i = 0; i < fieldCount; ++i) { Field oldField = oldFields[i]; Field newField = newFields[i]; if (oldField.accessFlags != newField.accessFlags) { return true; } FieldId oldFieldId = oldDex.fieldIds().get(oldField.fieldIndex); FieldId newFieldId = newDex.fieldIds().get(newField.fieldIndex); String oldFieldName = oldDex.strings().get(oldFieldId.nameIndex); String newFieldName = newDex.strings().get(newFieldId.nameIndex); if (!oldFieldName.equals(newFieldName)) { return true; } String oldFieldTypeName = oldDex.typeNames().get(oldFieldId.typeIndex); String newFieldTypeName = newDex.typeNames().get(newFieldId.typeIndex); if (!oldFieldTypeName.equals(newFieldTypeName)) { return true; } } return false; } private boolean isMethodsChangeAffectedToReferrer( Dex oldDex, Dex newDex, Method[] oldMethods, Method[] newMethods ) { if (oldMethods.length != newMethods.length) { return true; } int methodCount = oldMethods.length; for (int i = 0; i < methodCount; ++i) { Method oldMethod = oldMethods[i]; Method newMethod = newMethods[i]; if (oldMethod.accessFlags != newMethod.accessFlags) { return true; } MethodId oldMethodId = oldDex.methodIds().get(oldMethod.methodIndex); MethodId newMethodId = newDex.methodIds().get(newMethod.methodIndex); String oldMethodName = oldDex.strings().get(oldMethodId.nameIndex); String newMethodName = newDex.strings().get(newMethodId.nameIndex); if (!oldMethodName.equals(newMethodName)) { return true; } ProtoId oldProtoId = oldDex.protoIds().get(oldMethodId.protoIndex); ProtoId newProtoId = newDex.protoIds().get(newMethodId.protoIndex); String oldMethodShorty = oldDex.strings().get(oldProtoId.shortyIndex); String newMethodShorty = newDex.strings().get(newProtoId.shortyIndex); if (!oldMethodShorty.equals(newMethodShorty)) { return true; } String oldMethodReturnTypeName = oldDex.typeNames().get(oldProtoId.returnTypeIndex); String newMethodReturnTypeName = newDex.typeNames().get(newProtoId.returnTypeIndex); if (!oldMethodReturnTypeName.equals(newMethodReturnTypeName)) { return true; } short[] oldParameterIds = oldDex.parameterTypeIndicesFromMethodId(oldMethodId); short[] newParameterIds = newDex.parameterTypeIndicesFromMethodId(newMethodId); if (isTypeIdsChangeAffectedToReferrer( oldDex, newDex, oldParameterIds, newParameterIds, true )) { return true; } } return false; } private boolean isSameClass( Dex oldDex, Dex newDex, ClassDef oldClassDef, ClassDef newClassDef ) { if (oldClassDef.accessFlags != newClassDef.accessFlags) { return false; } if (!isSameClassDesc( oldDex, newDex, oldClassDef.supertypeIndex, newClassDef.supertypeIndex )) { return false; } short[] oldInterfaceIndices = oldDex.interfaceTypeIndicesFromClassDef(oldClassDef); short[] newInterfaceIndices = newDex.interfaceTypeIndicesFromClassDef(newClassDef); if (oldInterfaceIndices.length != newInterfaceIndices.length) { return false; } else { for (int i = 0; i < oldInterfaceIndices.length; ++i) { if (!isSameClassDesc(oldDex, newDex, oldInterfaceIndices[i], newInterfaceIndices[i])) { return false; } } } if (!isSameName(oldDex, newDex, oldClassDef.sourceFileIndex, newClassDef.sourceFileIndex)) { return false; } if (!isSameAnnotationDirectory( oldDex, newDex, oldClassDef.annotationsOffset, newClassDef.annotationsOffset )) { return false; } if (!isSameClassData( oldDex, newDex, oldClassDef.classDataOffset, newClassDef.classDataOffset )) { return false; } return isSameStaticValue( oldDex, newDex, oldClassDef.staticValuesOffset, newClassDef.staticValuesOffset ); } private boolean isSameStaticValue( Dex oldDex, Dex newDex, int oldStaticValueOffset, int newStaticValueOffset ) { if (oldStaticValueOffset == 0 && newStaticValueOffset == 0) { return true; } if (oldStaticValueOffset == 0 || newStaticValueOffset == 0) { return false; } EncodedValue oldStaticValue = oldDex.openSection(oldStaticValueOffset).readEncodedArray(); EncodedValue newStaticValue = newDex.openSection(newStaticValueOffset).readEncodedArray(); EncodedValueReader oldReader = new EncodedValueReader(oldStaticValue, EncodedValueReader.ENCODED_ARRAY); EncodedValueReader newReader = new EncodedValueReader(newStaticValue, EncodedValueReader.ENCODED_ARRAY); return isSameEncodedValue(oldDex, newDex, oldReader, newReader); } private boolean isSameClassDesc(Dex oldDex, Dex newDex, int oldTypeId, int newTypeId) { if (oldTypeId != ClassDef.NO_INDEX && newTypeId != ClassDef.NO_INDEX) { final String oldClassDesc = oldDex.typeNames().get(oldTypeId & 0xFFFF); final String newClassDesc = newDex.typeNames().get(newTypeId & 0xFFFF); return oldClassDesc.equals(newClassDesc); } else { return oldTypeId == ClassDef.NO_INDEX && newTypeId == ClassDef.NO_INDEX; } } private boolean isSameName(Dex oldDex, Dex newDex, int oldStringId, int newStringId) { if (oldStringId == TableOfContents.Section.UNDEF_INDEX && newStringId == TableOfContents.Section.UNDEF_INDEX) { return true; } if (oldStringId == TableOfContents.Section.UNDEF_INDEX || newStringId == TableOfContents.Section.UNDEF_INDEX) { return false; } return oldDex.strings().get(oldStringId).equals(newDex.strings().get(newStringId)); } private boolean isSameAnnotationDirectory( Dex oldDex, Dex newDex, int oldAnnotationDirectoryOffset, int newAnnotationDirectoryOffset ) { if (oldAnnotationDirectoryOffset == 0 && newAnnotationDirectoryOffset == 0) { return true; } if (oldAnnotationDirectoryOffset == 0 || newAnnotationDirectoryOffset == 0) { return false; } AnnotationsDirectory oldAnnotationsDirectory = oldDex.openSection(oldAnnotationDirectoryOffset).readAnnotationsDirectory(); AnnotationsDirectory newAnnotationsDirectory = newDex.openSection(newAnnotationDirectoryOffset).readAnnotationsDirectory(); if (!isSameAnnotationSet( oldDex, newDex, oldAnnotationsDirectory.classAnnotationsOffset, newAnnotationsDirectory.classAnnotationsOffset )) { return false; } int[][] oldFieldAnnotations = oldAnnotationsDirectory.fieldAnnotations; int[][] newFieldAnnotations = newAnnotationsDirectory.fieldAnnotations; if (oldFieldAnnotations.length != newFieldAnnotations.length) { return false; } for (int i = 0; i < oldFieldAnnotations.length; ++i) { if (!isSameFieldId( oldDex, newDex, oldFieldAnnotations[i][0], newFieldAnnotations[i][0] )) { return false; } if (!isSameAnnotationSet( oldDex, newDex, oldFieldAnnotations[i][1], newFieldAnnotations[i][1] )) { return false; } } int[][] oldMethodAnnotations = oldAnnotationsDirectory.methodAnnotations; int[][] newMethodAnnotations = newAnnotationsDirectory.methodAnnotations; if (oldMethodAnnotations.length != newMethodAnnotations.length) { return false; } for (int i = 0; i < oldMethodAnnotations.length; ++i) { if (!isSameMethodId( oldDex, newDex, oldMethodAnnotations[i][0], newMethodAnnotations[i][0] )) { return false; } if (!isSameAnnotationSet( oldDex, newDex, oldMethodAnnotations[i][1], newMethodAnnotations[i][1] )) { return false; } } int[][] oldParameterAnnotations = oldAnnotationsDirectory.parameterAnnotations; int[][] newParameterAnnotations = newAnnotationsDirectory.parameterAnnotations; if (oldParameterAnnotations.length != newParameterAnnotations.length) { return false; } for (int i = 0; i < oldParameterAnnotations.length; ++i) { if (!isSameMethodId( oldDex, newDex, oldParameterAnnotations[i][0], newParameterAnnotations[i][0] )) { return false; } if (!isSameAnnotationSetRefList( oldDex, newDex, oldParameterAnnotations[i][1], newParameterAnnotations[i][1] )) { return false; } } return true; } private boolean isSameFieldId(Dex oldDex, Dex newDex, int oldFieldIdIdx, int newFieldIdIdx) { FieldId oldFieldId = oldDex.fieldIds().get(oldFieldIdIdx); FieldId newFieldId = newDex.fieldIds().get(newFieldIdIdx); if (!isSameClassDesc( oldDex, newDex, oldFieldId.declaringClassIndex, newFieldId.declaringClassIndex )) { return false; } if (!isSameClassDesc( oldDex, newDex, oldFieldId.typeIndex, newFieldId.typeIndex )) { return false; } String oldName = oldDex.strings().get(oldFieldId.nameIndex); String newName = newDex.strings().get(newFieldId.nameIndex); return oldName.equals(newName); } private boolean isSameMethodId(Dex oldDex, Dex newDex, int oldMethodIdIdx, int newMethodIdIdx) { MethodId oldMethodId = oldDex.methodIds().get(oldMethodIdIdx); MethodId newMethodId = newDex.methodIds().get(newMethodIdIdx); if (!isSameClassDesc( oldDex, newDex, oldMethodId.declaringClassIndex, newMethodId.declaringClassIndex )) { return false; } if (!isSameProtoId(oldDex, newDex, oldMethodId.protoIndex, newMethodId.protoIndex)) { return false; } String oldName = oldDex.strings().get(oldMethodId.nameIndex); String newName = newDex.strings().get(newMethodId.nameIndex); return oldName.equals(newName); } private boolean isSameCallSiteId(Dex oldDex, Dex newDex, int oldCallSiteIdIdx, int newCallSiteIdIdx) { CallSiteId oldCallSiteId = oldDex.callsiteIds().get(oldCallSiteIdIdx); CallSiteId newCallSiteId = newDex.callsiteIds().get(newCallSiteIdIdx); return isSameStaticValue(oldDex, newDex, oldCallSiteId.offset, newCallSiteId.offset); } private boolean isSameMethodHandle(Dex oldDex, Dex newDex, int oldMethodHandleIdx, int newMethodHandleIdx) { MethodHandle oldMethodHandle = oldDex.methodHandles().get(oldMethodHandleIdx); MethodHandle newMethodHandle = newDex.methodHandles().get(newMethodHandleIdx); if (oldMethodHandle.methodHandleType != newMethodHandle.methodHandleType) { return false; } if (oldMethodHandle.methodHandleType.isField()) { if (!isSameFieldId(oldDex, newDex, oldMethodHandle.fieldOrMethodId, newMethodHandle.fieldOrMethodId)) { return false; } } else { if (!isSameMethodId(oldDex, newDex, oldMethodHandle.fieldOrMethodId, newMethodHandle.fieldOrMethodId)) { return false; } } if (oldMethodHandle.unused1 != newMethodHandle.unused1) { return false; } return oldMethodHandle.unused2 == newMethodHandle.unused2; } private boolean isSameProtoId(Dex oldDex, Dex newDex, int oldProtoIdIdx, int newProtoIdIdx) { ProtoId oldProtoId = oldDex.protoIds().get(oldProtoIdIdx); ProtoId newProtoId = newDex.protoIds().get(newProtoIdIdx); String oldShorty = oldDex.strings().get(oldProtoId.shortyIndex); String newShorty = newDex.strings().get(newProtoId.shortyIndex); if (!oldShorty.equals(newShorty)) { return false; } if (!isSameClassDesc( oldDex, newDex, oldProtoId.returnTypeIndex, newProtoId.returnTypeIndex )) { return false; } return isSameParameters( oldDex, newDex, oldProtoId.parametersOffset, newProtoId.parametersOffset ); } private boolean isSameParameters( Dex oldDex, Dex newDex, int oldParametersOffset, int newParametersOffset ) { if (oldParametersOffset == 0 && newParametersOffset == 0) { return true; } if (oldParametersOffset == 0 || newParametersOffset == 0) { return false; } TypeList oldParameters = oldDex.openSection(oldParametersOffset).readTypeList(); TypeList newParameters = newDex.openSection(newParametersOffset).readTypeList(); if (oldParameters.types.length != newParameters.types.length) { return false; } for (int i = 0; i < oldParameters.types.length; ++i) { if (!isSameClassDesc( oldDex, newDex, oldParameters.types[i], newParameters.types[i] )) { return false; } } return true; } private boolean isSameAnnotationSetRefList( Dex oldDex, Dex newDex, int oldAnnotationSetRefListOffset, int newAnnotationSetRefListOffset ) { if (oldAnnotationSetRefListOffset == 0 && newAnnotationSetRefListOffset == 0) { return true; } if (oldAnnotationSetRefListOffset == 0 || newAnnotationSetRefListOffset == 0) { return false; } AnnotationSetRefList oldAnnotationSetRefList = oldDex.openSection( oldAnnotationSetRefListOffset ).readAnnotationSetRefList(); AnnotationSetRefList newAnnotationSetRefList = newDex.openSection( newAnnotationSetRefListOffset ).readAnnotationSetRefList(); int oldAnnotationSetRefListCount = oldAnnotationSetRefList.annotationSetRefItems.length; int newAnnotationSetRefListCount = newAnnotationSetRefList.annotationSetRefItems.length; if (oldAnnotationSetRefListCount != newAnnotationSetRefListCount) { return false; } for (int i = 0; i < oldAnnotationSetRefListCount; ++i) { if (!isSameAnnotationSet( oldDex, newDex, oldAnnotationSetRefList.annotationSetRefItems[i], newAnnotationSetRefList.annotationSetRefItems[i] )) { return false; } } return true; } private boolean isSameAnnotationSet( Dex oldDex, Dex newDex, int oldAnnotationSetOffset, int newAnnotationSetOffset ) { if (oldAnnotationSetOffset == 0 && newAnnotationSetOffset == 0) { return true; } if (oldAnnotationSetOffset == 0 || newAnnotationSetOffset == 0) { return false; } AnnotationSet oldClassAnnotationSet = oldDex.openSection(oldAnnotationSetOffset).readAnnotationSet(); AnnotationSet newClassAnnotationSet = newDex.openSection(newAnnotationSetOffset).readAnnotationSet(); int oldAnnotationOffsetCount = oldClassAnnotationSet.annotationOffsets.length; int newAnnotationOffsetCount = newClassAnnotationSet.annotationOffsets.length; if (oldAnnotationOffsetCount != newAnnotationOffsetCount) { return false; } for (int i = 0; i < oldAnnotationOffsetCount; ++i) { if (!isSameAnnotation( oldDex, newDex, oldClassAnnotationSet.annotationOffsets[i], newClassAnnotationSet.annotationOffsets[i] )) { return false; } } return true; } private boolean isSameAnnotation( Dex oldDex, Dex newDex, int oldAnnotationOffset, int newAnnotationOffset ) { Annotation oldAnnotation = oldDex.openSection(oldAnnotationOffset).readAnnotation(); Annotation newAnnotation = newDex.openSection(newAnnotationOffset).readAnnotation(); if (oldAnnotation.visibility != newAnnotation.visibility) { return false; } EncodedValueReader oldAnnoReader = oldAnnotation.getReader(); EncodedValueReader newAnnoReader = newAnnotation.getReader(); return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader); } private boolean isSameAnnotationByReader( Dex oldDex, Dex newDex, EncodedValueReader oldAnnoReader, EncodedValueReader newAnnoReader ) { int oldFieldCount = oldAnnoReader.readAnnotation(); int newFieldCount = newAnnoReader.readAnnotation(); if (oldFieldCount != newFieldCount) { return false; } int oldAnnoType = oldAnnoReader.getAnnotationType(); int newAnnoType = newAnnoReader.getAnnotationType(); if (!isSameClassDesc(oldDex, newDex, oldAnnoType, newAnnoType)) { return false; } for (int i = 0; i < oldFieldCount; ++i) { int oldAnnoNameIdx = oldAnnoReader.readAnnotationName(); int newAnnoNameIdx = newAnnoReader.readAnnotationName(); if (!isSameName(oldDex, newDex, oldAnnoNameIdx, newAnnoNameIdx)) { return false; } if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) { return false; } } return true; } private boolean isSameEncodedValue( Dex oldDex, Dex newDex, EncodedValueReader oldAnnoReader, EncodedValueReader newAnnoReader ) { int oldAnnoItemType = oldAnnoReader.peek(); int newAnnoItemType = newAnnoReader.peek(); if (oldAnnoItemType != newAnnoItemType) { return false; } switch (oldAnnoItemType) { case EncodedValueReader.ENCODED_BYTE: { byte oldByte = oldAnnoReader.readByte(); byte newByte = newAnnoReader.readByte(); return oldByte == newByte; } case EncodedValueReader.ENCODED_SHORT: { short oldShort = oldAnnoReader.readShort(); short newShort = newAnnoReader.readShort(); return oldShort == newShort; } case EncodedValueReader.ENCODED_INT: { int oldInt = oldAnnoReader.readInt(); int newInt = newAnnoReader.readInt(); return oldInt == newInt; } case EncodedValueReader.ENCODED_LONG: { long oldLong = oldAnnoReader.readLong(); long newLong = newAnnoReader.readLong(); return oldLong == newLong; } case EncodedValueReader.ENCODED_CHAR: { char oldChar = oldAnnoReader.readChar(); char newChar = newAnnoReader.readChar(); return oldChar == newChar; } case EncodedValueReader.ENCODED_FLOAT: { float oldFloat = oldAnnoReader.readFloat(); float newFloat = newAnnoReader.readFloat(); return Float.compare(oldFloat, newFloat) == 0; } case EncodedValueReader.ENCODED_DOUBLE: { double oldDouble = oldAnnoReader.readDouble(); double newDouble = newAnnoReader.readDouble(); return Double.compare(oldDouble, newDouble) == 0; } case EncodedValueReader.ENCODED_STRING: { int oldStringIdx = oldAnnoReader.readString(); int newStringIdx = newAnnoReader.readString(); return isSameName(oldDex, newDex, oldStringIdx, newStringIdx); } case EncodedValueReader.ENCODED_TYPE: { int oldTypeId = oldAnnoReader.readType(); int newTypeId = newAnnoReader.readType(); return isSameClassDesc(oldDex, newDex, oldTypeId, newTypeId); } case EncodedValueReader.ENCODED_FIELD: { int oldFieldId = oldAnnoReader.readField(); int newFieldId = newAnnoReader.readField(); return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId); } case EncodedValueReader.ENCODED_ENUM: { int oldFieldId = oldAnnoReader.readEnum(); int newFieldId = newAnnoReader.readEnum(); return isSameFieldId(oldDex, newDex, oldFieldId, newFieldId); } case EncodedValueReader.ENCODED_METHOD: { int oldMethodId = oldAnnoReader.readMethod(); int newMethodId = newAnnoReader.readMethod(); return isSameMethodId(oldDex, newDex, oldMethodId, newMethodId); } case EncodedValueReader.ENCODED_ARRAY: { int oldArrSize = oldAnnoReader.readArray(); int newArrSize = newAnnoReader.readArray(); if (oldArrSize != newArrSize) { return false; } for (int i = 0; i < oldArrSize; ++i) { if (!isSameEncodedValue(oldDex, newDex, oldAnnoReader, newAnnoReader)) { return false; } } return true; } case EncodedValueReader.ENCODED_ANNOTATION: { return isSameAnnotationByReader(oldDex, newDex, oldAnnoReader, newAnnoReader); } case EncodedValueReader.ENCODED_NULL: { oldAnnoReader.readNull(); newAnnoReader.readNull(); return true; } case EncodedValueReader.ENCODED_BOOLEAN: { boolean oldBool = oldAnnoReader.readBoolean(); boolean newBool = newAnnoReader.readBoolean(); return oldBool == newBool; } default: { throw new IllegalStateException( "Unexpected annotation value type: " + Integer.toHexString(oldAnnoItemType) ); } } } private boolean isSameClassData( Dex oldDex, Dex newDex, int oldClassDataOffset, int newClassDataOffset ) { if (oldClassDataOffset == 0 && newClassDataOffset == 0) { return true; } if (oldClassDataOffset == 0 || newClassDataOffset == 0) { return false; } ClassData oldClassData = oldDex.openSection(oldClassDataOffset).readClassData(); ClassData newClassData = newDex.openSection(newClassDataOffset).readClassData(); ClassData.Field[] oldInstanceFields = oldClassData.instanceFields; ClassData.Field[] newInstanceFields = newClassData.instanceFields; if (oldInstanceFields.length != newInstanceFields.length) { return false; } for (int i = 0; i < oldInstanceFields.length; ++i) { if (!isSameField(oldDex, newDex, oldInstanceFields[i], newInstanceFields[i])) { return false; } } ClassData.Field[] oldStaticFields = oldClassData.staticFields; ClassData.Field[] newStaticFields = newClassData.staticFields; if (oldStaticFields.length != newStaticFields.length) { return false; } for (int i = 0; i < oldStaticFields.length; ++i) { if (!isSameField(oldDex, newDex, oldStaticFields[i], newStaticFields[i])) { return false; } } ClassData.Method[] oldDirectMethods = oldClassData.directMethods; ClassData.Method[] newDirectMethods = newClassData.directMethods; if (oldDirectMethods.length != newDirectMethods.length) { return false; } for (int i = 0; i < oldDirectMethods.length; ++i) { if (!isSameMethod(oldDex, newDex, oldDirectMethods[i], newDirectMethods[i])) { return false; } } ClassData.Method[] oldVirtualMethods = oldClassData.virtualMethods; ClassData.Method[] newVirtualMethods = newClassData.virtualMethods; if (oldVirtualMethods.length != newVirtualMethods.length) { return false; } for (int i = 0; i < oldVirtualMethods.length; ++i) { if (!isSameMethod(oldDex, newDex, oldVirtualMethods[i], newVirtualMethods[i])) { return false; } } return true; } private boolean isSameField( Dex oldDex, Dex newDex, ClassData.Field oldField, ClassData.Field newField ) { if (oldField.accessFlags != newField.accessFlags) { return false; } return isSameFieldId(oldDex, newDex, oldField.fieldIndex, newField.fieldIndex); } private boolean isSameMethod( Dex oldDex, Dex newDex, ClassData.Method oldMethod, ClassData.Method newMethod ) { if (oldMethod.accessFlags != newMethod.accessFlags) { return false; } if (!isSameMethodId(oldDex, newDex, oldMethod.methodIndex, newMethod.methodIndex)) { return false; } return isSameCode(oldDex, newDex, oldMethod.codeOffset, newMethod.codeOffset); } private boolean isSameCode( final Dex oldDex, final Dex newDex, int oldCodeOffset, int newCodeOffset ) { if (oldCodeOffset == 0 && newCodeOffset == 0) { return true; } if (oldCodeOffset == 0 || newCodeOffset == 0) { return false; } Code oldCode = oldDex.openSection(oldCodeOffset).readCode(); Code newCode = newDex.openSection(newCodeOffset).readCode(); if (oldCode.registersSize != newCode.registersSize) { return false; } if (oldCode.insSize != newCode.insSize) { return false; } final InstructionComparator insnComparator = new InstructionComparator( oldCode.instructions, newCode.instructions ) { @Override protected boolean compareString(int stringIndex1, int stringIndex2) { return isSameName(oldDex, newDex, stringIndex1, stringIndex2); } @Override protected boolean compareType(int typeIndex1, int typeIndex2) { return isSameClassDesc(oldDex, newDex, typeIndex1, typeIndex2); } @Override protected boolean compareField(int fieldIndex1, int fieldIndex2) { return isSameFieldId(oldDex, newDex, fieldIndex1, fieldIndex2); } @Override protected boolean compareMethod(int methodIndex1, int methodIndex2) { return isSameMethodId(oldDex, newDex, methodIndex1, methodIndex2); } @Override protected boolean compareCallSite(int callsiteIndex1, int callsiteIndex2) { return isSameCallSiteId(oldDex, newDex, callsiteIndex1, callsiteIndex2); } @Override protected boolean compareMethodHandle(int methodHandleIndex1, int methodHandleIndex2) { return isSameMethodHandle(oldDex, newDex, methodHandleIndex1, methodHandleIndex2); } @Override protected boolean compareProto(int protoIndex1, int protoIndex2) { return isSameProtoId(oldDex, newDex, protoIndex1, protoIndex2); } }; if (!insnComparator.compare()) { return false; } if (!isSameDebugInfo( oldDex, newDex, oldCode.debugInfoOffset, newCode.debugInfoOffset, insnComparator )) { return false; } return isSameTriesAndCatchHandlers(oldDex, newDex, oldCode.tries, newCode.tries, oldCode.catchHandlers, newCode.catchHandlers, insnComparator); } private boolean isSameDebugInfo( Dex oldDex, Dex newDex, int oldDebugInfoOffset, int newDebugInfoOffset, InstructionComparator insnComparator ) { if (oldDebugInfoOffset == 0 && newDebugInfoOffset == 0) { return true; } if (oldDebugInfoOffset == 0 || newDebugInfoOffset == 0) { return false; } DebugInfoItem oldDebugInfoItem = oldDex.openSection(oldDebugInfoOffset).readDebugInfoItem(); DebugInfoItem newDebugInfoItem = newDex.openSection(newDebugInfoOffset).readDebugInfoItem(); if (oldDebugInfoItem.lineStart != newDebugInfoItem.lineStart) { return false; } if (oldDebugInfoItem.parameterNames.length != newDebugInfoItem.parameterNames.length) { return false; } for (int i = 0; i < oldDebugInfoItem.parameterNames.length; ++i) { int oldNameIdx = oldDebugInfoItem.parameterNames[i]; int newNameIdx = newDebugInfoItem.parameterNames[i]; if (!isSameName(oldDex, newDex, oldNameIdx, newNameIdx)) { return false; } } DexDataBuffer oldDbgInfoBuffer = new DexDataBuffer(ByteBuffer.wrap(oldDebugInfoItem.infoSTM)); DexDataBuffer newDbgInfoBuffer = new DexDataBuffer(ByteBuffer.wrap(newDebugInfoItem.infoSTM)); int oldLine = oldDebugInfoItem.lineStart; int oldAddress = 0; int newLine = newDebugInfoItem.lineStart; int newAddress = 0; boolean isEnd = false; while (!isEnd && oldDbgInfoBuffer.available() > 0 && newDbgInfoBuffer.available() > 0) { int oldOpCode = oldDbgInfoBuffer.readUnsignedByte(); int newOpCode = newDbgInfoBuffer.readUnsignedByte(); if (oldOpCode != newOpCode) { if (oldOpCode < DBG_FIRST_SPECIAL || newOpCode < DBG_FIRST_SPECIAL) { return false; } } int currOpCode = oldOpCode; switch (currOpCode) { case DebugInfoItem.DBG_END_SEQUENCE: { isEnd = true; break; } case DebugInfoItem.DBG_ADVANCE_PC: { int oldAddrDiff = oldDbgInfoBuffer.readUleb128(); int newAddrDiff = newDbgInfoBuffer.readUleb128(); oldAddress += oldAddrDiff; newAddress += newAddrDiff; if (!insnComparator.isSameInstruction(oldAddress, newAddress)) { return false; } break; } case DebugInfoItem.DBG_ADVANCE_LINE: { int oldLineDiff = oldDbgInfoBuffer.readSleb128(); int newLineDiff = newDbgInfoBuffer.readSleb128(); oldLine += oldLineDiff; newLine += newLineDiff; if (oldLine != newLine) { return false; } break; } case DebugInfoItem.DBG_START_LOCAL: case DebugInfoItem.DBG_START_LOCAL_EXTENDED: { int oldRegisterNum = oldDbgInfoBuffer.readUleb128(); int newRegisterNum = newDbgInfoBuffer.readUleb128(); if (oldRegisterNum != newRegisterNum) { return false; } int oldNameIndex = oldDbgInfoBuffer.readUleb128p1(); int newNameIndex = newDbgInfoBuffer.readUleb128p1(); if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) { return false; } int oldTypeIndex = oldDbgInfoBuffer.readUleb128p1(); int newTypeIndex = newDbgInfoBuffer.readUleb128p1(); if (!isSameClassDesc(oldDex, newDex, oldTypeIndex, newTypeIndex)) { return false; } if (currOpCode == DebugInfoItem.DBG_START_LOCAL_EXTENDED) { int oldSigIndex = oldDbgInfoBuffer.readUleb128p1(); int newSigIndex = newDbgInfoBuffer.readUleb128p1(); if (!isSameName(oldDex, newDex, oldSigIndex, newSigIndex)) { return false; } } break; } case DebugInfoItem.DBG_END_LOCAL: case DebugInfoItem.DBG_RESTART_LOCAL: { int oldRegisterNum = oldDbgInfoBuffer.readUleb128(); int newRegisterNum = newDbgInfoBuffer.readUleb128(); if (oldRegisterNum != newRegisterNum) { return false; } break; } case DebugInfoItem.DBG_SET_FILE: { int oldNameIndex = oldDbgInfoBuffer.readUleb128p1(); int newNameIndex = newDbgInfoBuffer.readUleb128p1(); if (!isSameName(oldDex, newDex, oldNameIndex, newNameIndex)) { return false; } break; } case DebugInfoItem.DBG_SET_PROLOGUE_END: case DebugInfoItem.DBG_SET_EPILOGUE_BEGIN: { break; } default: { int oldAdjustedOpcode = oldOpCode - DBG_FIRST_SPECIAL; oldLine += DBG_LINE_BASE + (oldAdjustedOpcode % DBG_LINE_RANGE); oldAddress += (oldAdjustedOpcode / DBG_LINE_RANGE); int newAdjustedOpcode = newOpCode - DBG_FIRST_SPECIAL; newLine += DBG_LINE_BASE + (newAdjustedOpcode % DBG_LINE_RANGE); newAddress += (newAdjustedOpcode / DBG_LINE_RANGE); if (oldLine != newLine) { return false; } if (!insnComparator.isSameInstruction(oldAddress, newAddress)) { return false; } break; } } } if (oldDbgInfoBuffer.available() > 0 || newDbgInfoBuffer.available() > 0) { return false; } return true; } private boolean isSameTriesAndCatchHandlers( Dex oldDex, Dex newDex, Code.Try[] oldTries, Code.Try[] newTries, Code.CatchHandler[] oldHandlers, Code.CatchHandler[] newHandlers, InstructionComparator insnComparator ) { if (oldTries.length != newTries.length) { return false; } for (int i = 0; i < oldTries.length; ++i) { Code.Try oldTry = oldTries[i]; Code.Try newTry = newTries[i]; // Let InstructionComparator do this since it can translate 16-bit code unit count // into actual instruction count. // if (oldTry.instructionCount != newTry.instructionCount) { // return false; // } final Code.CatchHandler oldCatchHandler = oldHandlers[oldTry.catchHandlerIndex]; final Code.CatchHandler newCatchHandler = newHandlers[newTry.catchHandlerIndex]; if (!isSameCatchHandler(oldDex, newDex, oldCatchHandler, newCatchHandler, insnComparator)) { return false; } if (!insnComparator.isSameInstruction(oldTry.startAddress, newTry.startAddress)) { return false; } } return true; } private boolean isSameCatchHandler( Dex oldDex, Dex newDex, Code.CatchHandler oldCatchHandler, Code.CatchHandler newCatchHandler, InstructionComparator insnComparator ) { int oldTypeAddrPairCount = oldCatchHandler.typeIndexes.length; int newTypeAddrPairCount = newCatchHandler.typeIndexes.length; if (oldTypeAddrPairCount != newTypeAddrPairCount) { return false; } if (oldCatchHandler.catchAllAddress != -1 && newCatchHandler.catchAllAddress != -1) { return insnComparator.isSameInstruction( oldCatchHandler.catchAllAddress, newCatchHandler.catchAllAddress ); } else { if (!(oldCatchHandler.catchAllAddress == -1 && newCatchHandler.catchAllAddress == -1)) { return false; } } for (int j = 0; j < oldTypeAddrPairCount; ++j) { if (!isSameClassDesc( oldDex, newDex, oldCatchHandler.typeIndexes[j], newCatchHandler.typeIndexes[j] )) { return false; } if (!insnComparator.isSameInstruction( oldCatchHandler.addresses[j], newCatchHandler.addresses[j] )) { return false; } } return true; } public static final class DexClassInfo { public String classDesc = null; public int classDefIndex = ClassDef.NO_INDEX; public ClassDef classDef = null; public Dex owner = null; private DexClassInfo(String classDesc, int classDefIndex, ClassDef classDef, Dex owner) { this.classDesc = classDesc; this.classDef = classDef; this.classDefIndex = classDefIndex; this.owner = owner; } private DexClassInfo() { throw new UnsupportedOperationException(); } @Override public String toString() { return classDesc; } @Override public boolean equals(Object obj) { DexClassInfo other = (DexClassInfo) obj; if (!classDesc.equals(other.classDesc)) { return false; } return owner.computeSignature(false).equals(other.owner.computeSignature(false)); } @Override public int hashCode() { return owner.computeSignature(false).hashCode(); } } public static final class DexGroup { public final Dex[] dexes; private DexGroup(Dex... dexes) { if (dexes == null || dexes.length == 0) { throw new IllegalArgumentException("dexes is null or empty."); } this.dexes = new Dex[dexes.length]; System.arraycopy(dexes, 0, this.dexes, 0, dexes.length); } private DexGroup(File... dexFiles) throws IOException { if (dexFiles == null || dexFiles.length == 0) { throw new IllegalArgumentException("dexFiles is null or empty."); } this.dexes = new Dex[dexFiles.length]; for (int i = 0; i < dexFiles.length; ++i) { this.dexes[i] = new Dex(dexFiles[i]); } } private DexGroup(List dexFileList) throws IOException { if (dexFileList == null || dexFileList.isEmpty()) { throw new IllegalArgumentException("dexFileList is null or empty."); } this.dexes = new Dex[dexFileList.size()]; for (int i = 0; i < this.dexes.length; ++i) { this.dexes[i] = new Dex(dexFileList.get(i)); } } private DexGroup() { throw new UnsupportedOperationException(); } public static DexGroup wrap(Dex... dexes) { return new DexGroup(dexes); } public static DexGroup wrap(File... dexFiles) throws IOException { return new DexGroup(dexFiles); } public static DexGroup wrap(List dexFileList) throws IOException { return new DexGroup(dexFileList); } public Set getClassInfosInDexesWithDuplicateCheck() { Map classDescToInfoMap = new HashMap<>(); for (Dex dex : dexes) { int classDefIndex = 0; for (ClassDef classDef : dex.classDefs()) { String classDesc = dex.typeNames().get(classDef.typeIndex); if (!classDescToInfoMap.containsKey(classDesc)) { classDescToInfoMap.put(classDesc, new DexClassInfo(classDesc, classDefIndex, classDef, dex)); ++classDefIndex; } else { throw new IllegalStateException( String.format( "duplicate class descriptor [%s] in different dexes.", classDesc ) ); } } } return new HashSet<>(classDescToInfoMap.values()); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/DiffFactory.java ================================================ package com.tencent.tinker.build.util; import com.tencent.tinker.bsdiff.BSDiff; import com.tencent.tinker.build.patch.Configuration; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; public class DiffFactory { private static boolean diffShellPermission = false; public static void diffFile(Configuration config, File oldFile, File newFile, File diffFile) throws IOException { Logger.d("path:" + config.mCustomDiffPath + " oldFile:" + oldFile.getPath()); if (CustomDiff.checkHasCustomDiff(config)) { if (!diffShellPermission) { diffShellPermission = true; makeSurePermission(config.mCustomDiffPath); } CustomDiff.diffFile(config.mCustomDiffPath, config.mCustomDiffPathArgs, oldFile, newFile, diffFile); } else { BSDiff.bsdiff(oldFile, newFile, diffFile); } } private static void makeSurePermission(String path) throws IOException { try { Process process = new ProcessBuilder("chmod", "777", path.split(" ")[0]).start(); BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = br.readLine()) != null) { Logger.d(line); } int exitCode = process.waitFor(); Logger.d("run makeSurePermission done, exitCode: " + exitCode); process.destroy(); } catch (InterruptedException e) { e.printStackTrace(); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/ExcludedClassModifiedChecker.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.DexFormat; import com.tencent.tinker.build.dexpatcher.util.PatternUtils; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; /** * Created by tangyinsheng on 2016/4/14. */ public final class ExcludedClassModifiedChecker { private static final int STMCODE_START = 0x00; private static final int STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING = 0x01; private static final int STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING = 0x02; private static final int STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX = 0x03; private static final int STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH = 0x04; private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX = 0x05; private static final int STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX = 0x06; private static final int STMCODE_ERROR_LOADER_CLASS_CHANGED = 0x07; private static final int STMCODE_END = 0x08; private final Configuration config; private final DexClassesComparator dexCmptor; private Dex oldDex = null; private Dex newDex = null; private List deletedClassInfos = null; private List addedClassInfos = null; private Map changedClassInfosMap = null; private Set oldClassesDescToCheck = new HashSet<>(); private Set newClassesDescToCheck = new HashSet<>(); private HashSet ignoreChangeWarning = new HashSet<>(); public ExcludedClassModifiedChecker(Configuration config) { this.config = config; this.dexCmptor = new DexClassesComparator(config.mDexLoaderPattern); for (String classname : config.mDexIgnoreWarningLoaderPattern) { ignoreChangeWarning.add(Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(classname) )); } } public void checkIfExcludedClassWasModifiedInNewDex(File oldFile, File newFile) throws IOException, TinkerPatchException { if (oldFile == null && newFile == null) { throw new TinkerPatchException("both oldFile and newFile are null."); } oldDex = (oldFile != null ? new Dex(oldFile) : null); newDex = (newFile != null ? new Dex(newFile) : null); int stmCode = STMCODE_START; while (stmCode != STMCODE_END) { switch (stmCode) { /** * Check rule: * Loader classes must only appear in primary dex and each of them in primary old dex should keep * completely consistent in new primary dex. * * An error is announced when any of these conditions below is fit: * 1. Primary old dex is missing. * 2. Primary new dex is missing. * 3. There are not any loader classes in primary old dex. * 4. There are some new loader classes added in new primary dex. * 5. Loader classes in old primary dex are modified in new primary dex. * 6. Loader classes are found in secondary old dexes. * 7. Loader classes are found in secondary new dexes. */ case STMCODE_START: { boolean isPrimaryDex = isPrimaryDex((oldFile == null ? newFile : oldFile)); if (isPrimaryDex) { if (oldFile == null) { stmCode = STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING; } else if (newFile == null) { stmCode = STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING; } else { dexCmptor.startCheck(oldDex, newDex); deletedClassInfos = dexCmptor.getDeletedClassInfos(); addedClassInfos = dexCmptor.getAddedClassInfos(); changedClassInfosMap = new HashMap<>(dexCmptor.getChangedClassDescToInfosMap()); // All loader classes are in new dex, while none of them in old one. if (deletedClassInfos.isEmpty() && changedClassInfosMap.isEmpty() && !addedClassInfos.isEmpty()) { stmCode = STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX; } else { if (addedClassInfos.isEmpty()) { // class descriptor is completely matches, see if any contents changes. ArrayList removeClasses = new ArrayList<>(); for (String classname : changedClassInfosMap.keySet()) { if (Utils.checkFileInPattern(ignoreChangeWarning, classname)) { Logger.e("loader class pattern: " + classname + " has changed, but it match ignore change pattern, just ignore!"); removeClasses.add(classname); } } changedClassInfosMap.keySet().removeAll(removeClasses); if (changedClassInfosMap.isEmpty()) { stmCode = STMCODE_END; } else { stmCode = STMCODE_ERROR_LOADER_CLASS_CHANGED; } } else { stmCode = STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH; } } } } else { Set patternsOfClassDescToCheck = new HashSet<>(); for (String patternStr : config.mDexLoaderPattern) { patternsOfClassDescToCheck.add( Pattern.compile( PatternUtils.dotClassNamePatternToDescriptorRegEx(patternStr) ) ); } if (oldDex != null) { oldClassesDescToCheck.clear(); for (ClassDef classDef : oldDex.classDefs()) { String desc = oldDex.typeNames().get(classDef.typeIndex); if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { oldClassesDescToCheck.add(desc); } } if (!oldClassesDescToCheck.isEmpty()) { stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX; break; } } if (newDex != null) { newClassesDescToCheck.clear(); for (ClassDef classDef : newDex.classDefs()) { String desc = newDex.typeNames().get(classDef.typeIndex); if (Utils.isStringMatchesPatterns(desc, patternsOfClassDescToCheck)) { newClassesDescToCheck.add(desc); } } if (!newClassesDescToCheck.isEmpty()) { stmCode = STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX; break; } } stmCode = STMCODE_END; } break; } case STMCODE_ERROR_PRIMARY_OLD_DEX_IS_MISSING: { throw new TinkerPatchException("old primary dex is missing."); } case STMCODE_ERROR_PRIMARY_NEW_DEX_IS_MISSING: { throw new TinkerPatchException("new primary dex is missing."); } case STMCODE_ERROR_LOADER_CLASS_NOT_IN_PRIMARY_OLD_DEX: { final String msg = "all loader classes don't appear in old primary dex."; if (config.mAllowLoaderInAnyDex) { Logger.d(msg); return; } else { throw new TinkerPatchException(msg); } } case STMCODE_ERROR_LOADER_CLASS_IN_PRIMARY_DEX_MISMATCH: { throw new TinkerPatchException( "there's loader classes added in new primary dex, such these changes will not take effect.\n" + "added classes: " + Utils.collectionToString(addedClassInfos) ); } case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_OLD_DEX: { final String msg = "loader classes are found in old secondary dex. Found classes: " + Utils.collectionToString(oldClassesDescToCheck); if (config.mAllowLoaderInAnyDex) { Logger.d(msg); return; } else { throw new TinkerPatchException(msg); } } case STMCODE_ERROR_LOADER_CLASS_FOUND_IN_SECONDARY_NEW_DEX: { final String msg = "loader classes are found in new secondary dex. Found classes: " + Utils.collectionToString(newClassesDescToCheck); if (config.mAllowLoaderInAnyDex) { Logger.d(msg); return; } else { throw new TinkerPatchException(msg); } } case STMCODE_ERROR_LOADER_CLASS_CHANGED: { String msg = "some loader class has been changed in new primary dex." + " Such these changes will not take effect!!" + " related classes: " + Utils.collectionToString(changedClassInfosMap.keySet()); throw new TinkerPatchException(msg); } default: { Logger.e("internal-error: unexpected stmCode."); stmCode = STMCODE_END; break; } } } } public boolean isPrimaryDex(File dexFile) { Path dexFilePath = dexFile.toPath(); Path parentPath = config.mTempUnzipOldDir.toPath(); if (!dexFilePath.startsWith(parentPath)) { parentPath = config.mTempUnzipNewDir.toPath(); } return DexFormat.DEX_IN_JAR_NAME.equals(parentPath.relativize(dexFilePath).toString().replace('\\', '/')); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/FileOperation.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.commons.util.IOHelper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; public class FileOperation { public static final boolean deleteFile(String filePath) { if (filePath == null) { return true; } File file = new File(filePath); if (file.exists()) { return file.delete(); } return true; } public static final boolean deleteFile(File file) { if (file == null) { return true; } if (file.exists()) { return file.delete(); } return true; } public static boolean isLegalFile(String path) { if (path == null) { return false; } File file = new File(path); return file.exists() && file.isFile() && file.length() > 0; } public static boolean isLegalFileOrDirectory(String path) { if (isLegalFile(path)) { return true; } if (path == null) { return false; } final File file = new File(path); return file.exists() && file.isDirectory() && file.canRead(); } public static long getFileSizes(File f) { if (f == null) { return 0; } long size = 0; if (f.exists() && f.isFile()) { InputStream fis = null; try { fis = new BufferedInputStream(new FileInputStream(f)); size = fis.available(); } catch (IOException e) { e.printStackTrace(); } finally { IOHelper.closeQuietly(fis); } } return size; } public static final boolean deleteDir(File file) { if (file == null || (!file.exists())) { return false; } if (file.isFile()) { file.delete(); } else if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { deleteDir(files[i]); } } file.delete(); return true; } public static void cleanDir(File dir) { if (dir.exists()) { FileOperation.deleteDir(dir); dir.mkdirs(); } } public static void copyResourceUsingStream(String name, File dest) throws IOException { FileOutputStream os = null; File parent = dest.getParentFile(); if (parent != null && (!parent.exists())) { parent.mkdirs(); } InputStream is = null; try { is = FileOperation.class.getResourceAsStream("/" + name); os = new FileOutputStream(dest, false); byte[] buffer = new byte[TypedValue.BUFFER_SIZE]; int length; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } } finally { IOHelper.closeQuietly(os); IOHelper.closeQuietly(is); } } public static void copyFileUsingStream(File source, File dest) throws IOException { FileInputStream is = null; FileOutputStream os = null; File parent = dest.getParentFile(); if (parent != null && (!parent.exists())) { parent.mkdirs(); } try { is = new FileInputStream(source); os = new FileOutputStream(dest, false); byte[] buffer = new byte[TypedValue.BUFFER_SIZE]; int length; while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length); } } finally { IOHelper.closeQuietly(os); IOHelper.closeQuietly(is); } } public static boolean checkDirectory(String dir) { File dirObj = new File(dir); deleteDir(dirObj); if (!dirObj.exists()) { dirObj.mkdirs(); } return true; } @SuppressWarnings("rawtypes") public static void unZipAPk(String fileName, String filePath) throws IOException { checkDirectory(filePath); ZipFile zipFile = new ZipFile(fileName); Enumeration enumeration = zipFile.entries(); try { while (enumeration.hasMoreElements()) { ZipEntry entry = (ZipEntry) enumeration.nextElement(); if (!validateZipEntryName(new File(filePath), entry.getName())) { throw new IOException("Bad ZipEntry name: " + entry.getName()); } if (entry.isDirectory()) { new File(filePath, entry.getName()).mkdirs(); continue; } BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); File file = new File(filePath + File.separator + entry.getName()); File parentFile = file.getParentFile(); if (parentFile != null && (!parentFile.exists())) { parentFile.mkdirs(); } FileOutputStream fos = null; BufferedOutputStream bos = null; try { fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos, TypedValue.BUFFER_SIZE); byte[] buf = new byte[TypedValue.BUFFER_SIZE]; int len; while ((len = bis.read(buf, 0, TypedValue.BUFFER_SIZE)) != -1) { fos.write(buf, 0, len); } } finally { if (bos != null) { bos.flush(); bos.close(); } if (bis != null) { bis.close(); } } } } finally { if (zipFile != null) { zipFile.close(); } } } /** * zip list of file * * @param resFileList file(dir) list * @param zipFile output zip file * @throws IOException */ public static void zipFiles(Collection resFileList, File zipFile, String comment) throws IOException { ZipOutputStream zipout = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile), TypedValue.BUFFER_SIZE)); for (File resFile : resFileList) { if (resFile.exists()) { zipFile(resFile, zipout, ""); } } if (comment != null) { zipout.setComment(comment); } zipout.close(); } private static void zipFile(File resFile, ZipOutputStream zipout, String rootpath) throws IOException { rootpath = rootpath + (rootpath.trim().length() == 0 ? "" : File.separator) + resFile.getName(); if (resFile.isDirectory()) { File[] fileList = resFile.listFiles(); for (File file : fileList) { zipFile(file, zipout, rootpath); } } else { final byte[] fileContents = readContents(resFile); //linux format!! if (rootpath.contains("\\")) { rootpath = rootpath.replace("\\", "/"); } ZipEntry entry = new ZipEntry(rootpath); // if (compressMethod == ZipEntry.DEFLATED) { entry.setMethod(ZipEntry.DEFLATED); // } else { // entry.setMethod(ZipEntry.STORED); // entry.setSize(fileContents.length); // final CRC32 checksumCalculator = new CRC32(); // checksumCalculator.update(fileContents); // entry.setCrc(checksumCalculator.getValue()); // } zipout.putNextEntry(entry); zipout.write(fileContents); zipout.flush(); zipout.closeEntry(); } } private static byte[] readContents(final File file) throws IOException { final ByteArrayOutputStream output = new ByteArrayOutputStream(); final int bufferSize = TypedValue.BUFFER_SIZE; InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); int length; byte[] buffer = new byte[bufferSize]; byte[] bufferCopy; while ((length = in.read(buffer, 0, bufferSize)) > 0) { bufferCopy = new byte[length]; System.arraycopy(buffer, 0, bufferCopy, 0, length); output.write(bufferCopy); } } finally { IOHelper.closeQuietly(output); IOHelper.closeQuietly(in); } return output.toByteArray(); } public static long getFileCrc32(File file) throws IOException { InputStream inputStream = null; try { inputStream = new BufferedInputStream(new FileInputStream(file)); CRC32 crc = new CRC32(); int cnt; while ((cnt = inputStream.read()) != -1) { crc.update(cnt); } return crc.getValue(); } finally { IOHelper.closeQuietly(inputStream); } } public static String getZipEntryCrc(File file, String entryName) { ZipFile zipFile = null; try { zipFile = new ZipFile(file); ZipEntry entry = zipFile.getEntry(entryName); if (entry == null) { return null; } return String.valueOf(entry.getCrc()); } catch (IOException e) { e.printStackTrace(); } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } public static String getZipEntryMd5(File file, String entryName) { ZipFile zipFile = null; try { zipFile = new ZipFile(file); ZipEntry entry = zipFile.getEntry(entryName); if (entry == null) { return null; } return MD5.getMD5(zipFile.getInputStream(entry), 1024 * 100); } catch (IOException e) { e.printStackTrace(); } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } public static void zipInputDir(File inputDir, File outputFile, String comment) throws IOException { File[] unzipFiles = inputDir.listFiles(); List collectFiles = new ArrayList<>(); for (File f : unzipFiles) { collectFiles.add(f); } FileOperation.zipFiles(collectFiles, outputFile, comment); } public static boolean sevenZipInputDir(File inputDir, File outputFile, Configuration config) { String outPath = inputDir.getAbsolutePath(); String path = outPath + File.separator + "*"; String cmd = config.mSevenZipPath; ProcessBuilder pb = new ProcessBuilder(cmd, "a", "-tzip", outputFile.getAbsolutePath(), path, "-mx9"); pb.redirectErrorStream(true); Process pro = null; LineNumberReader reader = null; try { pro = pb.start(); reader = new LineNumberReader(new InputStreamReader(pro.getInputStream())); while (reader.readLine() != null) { } } catch (IOException e) { FileOperation.deleteFile(outputFile); Logger.e("7a patch file failed, you should set the zipArtifact, or set the path directly"); return false; } finally { //destroy the stream try { pro.waitFor(); } catch (Throwable ignored) { // Ignored. } try { pro.destroy(); } catch (Throwable ignored) { // Ignored. } IOHelper.closeQuietly(reader); } return true; } private static boolean validateZipEntryName(File destDir, String entryName) { if (entryName == null || entryName.isEmpty()) { return false; } try { final String canonicalDestinationDir = destDir.getCanonicalPath(); final File destEntryFile = destDir.toPath().resolve(entryName).toFile(); return destEntryFile.getCanonicalPath().startsWith(canonicalDestinationDir + File.separator); } catch (Throwable ignored) { return false; } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Logger.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.build.info.InfoWriter; import com.tencent.tinker.build.patch.Configuration; import java.io.File; import java.io.IOException; /** * Created by zhangshaowen on 16/4/7. */ public class Logger { private static InfoWriter logWriter; public static void initLogger(Configuration config) throws IOException { String logPath = config.mOutFolder + File.separator + TypedValue.FILE_LOG; logWriter = new InfoWriter(config, logPath); } public static void closeLogger() { if (logWriter != null) { logWriter.close(); } } public static void d(final String msg) { Logger.d("%s", msg); } public static void d(final String format, final Object... obj) { String log = obj.length == 0 ? format : String.format(format, obj); if (log == null) { log = ""; } System.out.println(log); System.out.flush(); logWriter.writeLineToInfoFile(log); } public static void e(final String msg) { Logger.e("%s", msg); } public static void e(final String format, final Object... obj) { String log = obj.length == 0 ? format : String.format(format, obj); if (log == null) { log = ""; } System.err.println(log); System.err.flush(); logWriter.writeLineToInfoFile(log); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/MD5.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.commons.util.IOHelper; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.MessageDigest; /** *
MD5 digest wrapper
*
MD5计算封装
* * @author zhaoyuan */ public final class MD5 { private MD5() { } /** * get md5 string for input buffer * * @param buffer data to be calculated * @return md5 result in string format */ public static String getMessageDigest(byte[] buffer) { char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(buffer); byte[] md = mdTemp.digest(); int j = md.length; char[] str = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { return null; } } /** * get md5 in byte array * * @param buffer data to be calculated * @return md5 result in byte array format */ public static byte[] getRawDigest(byte[] buffer) { try { MessageDigest mdTemp = MessageDigest.getInstance("MD5"); mdTemp.update(buffer); return mdTemp.digest(); } catch (Exception e) { return null; } } /** * Get the md5 for inputStream. * This method cost less memory. It read bufLen bytes from the FileInputStream once. * * @param is * @param bufLen bytes number read from the stream once. * The less bufLen is the more times getMD5() method takes. Also the less bufLen is the less memory cost. */ public static String getMD5(final InputStream is, final int bufLen, final int offset, final int length) { return getMD5ExtendBytes(is, bufLen, offset, length, null); } /** * Get the md5 for inputStream. * This method cost less memory. It read bufLen bytes from the FileInputStream once. * * @param is * @param bufLen * @param offset * @param length * @param extendBytes extend bytes which would be add to the end of input stream for MD5 calculating */ public static String getMD5ExtendBytes(final InputStream is, final int bufLen, final int offset, final int length, final byte[] extendBytes) { if (is == null || bufLen <= 0 || offset < 0 || length <= 0) { return null; } try { long skipLen = is.skip(offset); if (skipLen < offset) { // reach the end return null; } MessageDigest md = MessageDigest.getInstance("MD5"); StringBuilder md5Str = new StringBuilder(32); byte[] buf = new byte[bufLen]; int readCount = 0; int totalRead = 0; while ((readCount = is.read(buf)) != -1 && totalRead < length) { if (totalRead + readCount <= length) { md.update(buf, 0, readCount); totalRead += readCount; } else { md.update(buf, 0, length - totalRead); totalRead = length; } } if (extendBytes != null && extendBytes.length > 0) { md.update(extendBytes); } byte[] hashValue = md.digest(); for (int i = 0; i < hashValue.length; i++) { md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); } return md5Str.toString(); } catch (Exception e) { return null; } } /** * Get the md5 for inputStream. * This method cost less memory. It read bufLen bytes from the FileInputStream once. * * @param is * @param bufLen bytes number read from the stream once. * The less bufLen is the more times getMD5() method takes. Also the less bufLen is the less memory cost. */ public static String getMD5(final InputStream is, final int bufLen) { if (is == null || bufLen <= 0) { return null; } try { MessageDigest md = MessageDigest.getInstance("MD5"); StringBuilder md5Str = new StringBuilder(32); byte[] buf = new byte[bufLen]; int readCount = 0; while ((readCount = is.read(buf)) != -1) { md.update(buf, 0, readCount); } byte[] hashValue = md.digest(); for (int i = 0; i < hashValue.length; i++) { md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1)); } return md5Str.toString(); } catch (Exception e) { return null; } } /** * Get the md5 for the file, using less memory. */ public static String getMD5(final String file) { if (file == null) { return null; } File f = new File(file); if (f.exists()) { return getMD5(f, 1024 * 100); } return null; } /** * Get the md5 for the file, using less memory. */ public static String getMD5(final File file) { return getMD5(file, 1024 * 100); } /** * Get the md5 for the file. call getMD5(FileInputStream is, int bufLen) inside. * * @param file * @param bufLen bytes number read from the stream once. * The less bufLen is the more times getMD5() method takes. Also the less bufLen cost less memory. */ public static String getMD5(final File file, final int bufLen) { if (file == null || bufLen <= 0 || !file.exists()) { return null; } FileInputStream fin = null; try { fin = new FileInputStream(file); String md5 = MD5.getMD5(fin, (int) (bufLen <= file.length() ? bufLen : file.length())); fin.close(); return md5; } catch (Exception e) { return null; } finally { IOHelper.closeQuietly(fin); } } /** * Get the md5 for the file, using less memory. */ public static String getMD5(final String file, final int offset, final int length) { if (file == null) { return null; } File f = new File(file); if (f.exists()) { return getMD5(f, offset, length); } return null; } /** * Get the md5 for the file, using less memory. */ public static String getMD5(final File file, final int offset, final int length) { if (file == null || !file.exists() || offset < 0 || length <= 0) { return null; } FileInputStream fin = null; try { fin = new FileInputStream(file); String md5 = MD5.getMD5(fin, 1024 * 100, offset, length); fin.close(); return md5; } catch (Exception e) { return null; } finally { IOHelper.closeQuietly(fin); } } public static String getMD5ExtendBytes(final File file, final int offset, final int length, byte[] extend) { if (file == null || !file.exists() || offset < 0 || length <= 0) { return null; } FileInputStream fin = null; try { fin = new FileInputStream(file); String md5 = MD5.getMD5ExtendBytes(fin, 1024 * 100, offset, length, extend); fin.close(); return md5; } catch (Exception e) { return null; } finally { IOHelper.closeQuietly(fin); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TinkerPatchException.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; /** * @author zhangshaowen */ public class TinkerPatchException extends RuntimeException { private static final long serialVersionUID = 1L; public TinkerPatchException() { } public TinkerPatchException(String message) { super(message); } public TinkerPatchException(String message, Throwable cause) { super(message, cause); } public TinkerPatchException(Throwable cause) { super(cause); } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/TypedValue.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; /** * Container for a dynamically typed data value. Primarily used with */ public class TypedValue { public static final int BUFFER_SIZE = 16384; public static final int K_BYTES = 1024; public static final String FILE_TXT = ".txt"; public static final String FILE_XML = ".xml"; public static final String FILE_APK = ".apk"; public static final String FILE_CONFIG = "config.xml"; public static final String FILE_LOG = "log.txt"; public static final String SO_LOG_FILE = "so_log.txt"; public static final String SO_META_FILE = "so_meta.txt"; public static final String DEX_LOG_FILE = "dex_log.txt"; public static final String DEX_META_FILE = "dex_meta.txt"; public static final String DEX_TEMP_PATCH_DIR = "tempPatchedDexes"; public static final String RES_LOG_FILE = "res_log.txt"; public static final String RES_META_TXT = "res_meta.txt"; public static final String ARKHOT_META_TXT = "arkHot_meta.txt"; public static final String FILE_ASSETS = "assets"; public static final String TINKER_ID = "TINKER_ID"; public static final String NEW_TINKER_ID = "NEW_TINKER_ID"; // Please keep it synchronized with the one defined in ShareConstants. public static final String PKGMETA_KEY_IS_PROTECTED_APP = "is_protected_app"; public static final String PKGMETA_KEY_USE_CUSTOM_FILE_PATCH = "use_custom_file_patch"; public static final String PACKAGE_META_FILE = "package_meta.txt"; // Please keep it synchronized with the other one defined in 'EnvConsts' class public static final String INCCOMPONENT_META_FILE = "assets/inc_component_meta.txt"; public static final String PATH_DEFAULT_OUTPUT = "tinkerPatch"; public static final String PATH_PATCH_FILES = "tinker_result"; public static final String OUT_7ZIP_FILE_PATH = "out_7zip"; public static final int ANDROID_40_API_LEVEL = 14; public static final double DEX_PATCH_MAX_RATIO = 0.6; public static final double DEX_JAR_PATCH_MAX_RATIO = 1.0; public static final double BSDIFF_PATCH_MAX_RATIO = 0.8; public static final String RES_ARSC = "resources.arsc"; public static final String RES_MANIFEST = "AndroidManifest.xml"; public static final String RES_OUT = "resources_out.zip"; public static final String RES_OUT_7ZIP = "resources_out_7z.zip"; public static final int ADD = 1; public static final int MOD = 2; public static final int DEL = 3; public static final int LARGE_MOD = 4; public static final int STORED = 5; public static final String ADD_TITLE = "add:"; public static final String MOD_TITLE = "modify:"; public static final String LARGE_MOD_TITLE = "large modify:"; public static final String DEL_TITLE = "delete:"; public static final String PATTERN_TITLE = "pattern:"; public static final String STORE_TITLE = "store:"; } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/com/tencent/tinker/build/util/Utils.java ================================================ /* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.util; import com.tencent.tinker.build.decoder.ResDiffDecoder; import com.tencent.tinker.build.patch.Configuration; import com.tencent.tinker.commons.util.IOHelper; import com.tencent.tinker.ziputils.ziputil.TinkerZipEntry; import com.tencent.tinker.ziputils.ziputil.TinkerZipFile; import com.tencent.tinker.ziputils.ziputil.TinkerZipOutputStream; import com.tencent.tinker.ziputils.ziputil.TinkerZipUtil; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.regex.Pattern; /** * Created by sun on 1/9/16. */ public class Utils { public static boolean isPresent(String str) { return str != null && str.length() > 0; } public static boolean isBlank(String str) { return !isPresent(str); } public static boolean isPresent(Iterator iterator) { return iterator != null && iterator.hasNext(); } public static boolean isBlank(Iterator iterator) { return !isPresent(iterator); } public static String convertToPatternString(String input) { //convert \\. if (input.contains(".")) { input = input.replaceAll("\\.", "\\\\."); } //convert ?to . if (input.contains("?")) { input = input.replaceAll("\\?", "\\."); } //convert * to.* if (input.contains("*")) { input = input.replace("*", ".*"); } return input; } public static boolean isNullOrNil(final String object) { return (object == null) || (object.length() <= 0); } public static boolean isNullOrNil(final Collection collection) { return (collection == null || collection.isEmpty()); } public static boolean isStringMatchesPatterns(String str, Collection patterns) { for (Pattern pattern : patterns) { if (pattern.matcher(str).matches()) { return true; } } return false; } public static String collectionToString(Collection collection) { StringBuilder sb = new StringBuilder(); sb.append('{'); boolean isFirstElement = true; for (T element : collection) { if (isFirstElement) { isFirstElement = false; } else { sb.append(','); } sb.append(element); } sb.append('}'); return sb.toString(); } public static boolean checkFileInPattern(HashSet patterns, String key) { if (!patterns.isEmpty()) { for (Iterator it = patterns.iterator(); it.hasNext();) { Pattern p = it.next(); if (p.matcher(key).matches()) { return true; } } } return false; } public static String genResOutputFile(File output, File newZipFile, Configuration config, ArrayList addedSet, ArrayList modifiedSet, ArrayList deletedSet, ArrayList largeModifiedSet, HashMap largeModifiedMap) throws IOException { TinkerZipFile oldApk = null; TinkerZipFile newApk = null; TinkerZipOutputStream out = null; try { oldApk = new TinkerZipFile(config.mOldApkFile); newApk = new TinkerZipFile(newZipFile); out = new TinkerZipOutputStream(new BufferedOutputStream(new FileOutputStream(output))); final Enumeration entries = oldApk.entries(); while (entries.hasMoreElements()) { TinkerZipEntry zipEntry = entries.nextElement(); if (zipEntry == null) { throw new TinkerPatchException( String.format("zipEntry is null when get from oldApk") ); } String name = zipEntry.getName(); if (!TinkerZipUtil.validateZipEntryName(output.getParentFile(), name)) { throw new IOException("Bad ZipEntry name: " + name); } if (Utils.checkFileInPattern(config.mResFilePattern, name)) { //won't contain in add set. if (!deletedSet.contains(name) && !modifiedSet.contains(name) && !largeModifiedSet.contains(name) && !name.equals(TypedValue.RES_MANIFEST)) { TinkerZipUtil.extractTinkerEntry(oldApk, zipEntry, out); } } } //process manifest TinkerZipEntry manifestZipEntry = oldApk.getEntry(TypedValue.RES_MANIFEST); if (manifestZipEntry == null) { throw new TinkerPatchException( String.format("can't found resource file %s from old apk file %s", TypedValue.RES_MANIFEST, config.mOldApkFile.getAbsolutePath()) ); } TinkerZipUtil.extractTinkerEntry(oldApk, manifestZipEntry, out); for (String name : largeModifiedSet) { TinkerZipEntry largeZipEntry = oldApk.getEntry(name); if (largeZipEntry == null) { throw new TinkerPatchException( String.format("can't found resource file %s from old apk file %s", name, config.mOldApkFile.getAbsolutePath()) ); } ResDiffDecoder.LargeModeInfo largeModeInfo = largeModifiedMap.get(name); TinkerZipUtil.extractLargeModifyFile(largeZipEntry, largeModeInfo.path, largeModeInfo.crc, out); } for (String name : addedSet) { TinkerZipEntry addZipEntry = newApk.getEntry(name); if (addZipEntry == null) { throw new TinkerPatchException( String.format("can't found add resource file %s from new apk file %s", name, config.mNewApkFile.getAbsolutePath()) ); } TinkerZipUtil.extractTinkerEntry(newApk, addZipEntry, out); } for (String name : modifiedSet) { TinkerZipEntry modZipEntry = newApk.getEntry(name); if (modZipEntry == null) { throw new TinkerPatchException( String.format("can't found add resource file %s from new apk file %s", name, config.mNewApkFile.getAbsolutePath()) ); } TinkerZipUtil.extractTinkerEntry(newApk, modZipEntry, out); } } finally { IOHelper.closeQuietly(out); IOHelper.closeQuietly(oldApk); IOHelper.closeQuietly(newApk); } return MD5.getMD5(output); } public static String getResourceMeta(String baseCrc, String md5) { return TypedValue.RES_OUT + "," + baseCrc + "," + md5; } /** * if bsDiff result is too larger, just treat it as newly file * @param bsDiffFile * @param newFile * @return */ public static boolean checkBsDiffFileSize(File bsDiffFile, File newFile) { if (!bsDiffFile.exists()) { throw new TinkerPatchException("can not find the bsDiff file:" + bsDiffFile.getAbsolutePath()); } //check bsDiffFile file size double ratio = bsDiffFile.length() / (double) newFile.length(); if (ratio > TypedValue.BSDIFF_PATCH_MAX_RATIO) { Logger.e("bsDiff patch file:%s, size:%dk, new file:%s, size:%dk. patch file is too large, treat it as newly file to save patch time!", bsDiffFile.getName(), bsDiffFile.length() / 1024, newFile.getName(), newFile.length() / 1024 ); return false; } return true; } public static void closeQuietly(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: tinker-build/tinker-patch-lib/src/main/java/org/jf/dexlib2/builder/BuilderMutableMethodImplementation.java ================================================ /* * Copyright 2013, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER 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. */ /** * *** This file is NOT a part of DexLib2 project. *** * * Tricky ways for converting MethodImplementation in DexFile into the corresponding * one in DexBuilder. * * If you pass null as DexBuilder, this class behavior the same as * what {@link org.jf.dexlib2.builder.MutableMethodImplementation} would do. */ package org.jf.dexlib2.builder; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.jf.dexlib2.DebugItemType; import org.jf.dexlib2.Opcode; import org.jf.dexlib2.builder.debug.BuilderEndLocal; import org.jf.dexlib2.builder.debug.BuilderEpilogueBegin; import org.jf.dexlib2.builder.debug.BuilderLineNumber; import org.jf.dexlib2.builder.debug.BuilderPrologueEnd; import org.jf.dexlib2.builder.debug.BuilderRestartLocal; import org.jf.dexlib2.builder.debug.BuilderSetSourceFile; import org.jf.dexlib2.builder.debug.BuilderStartLocal; import org.jf.dexlib2.builder.instruction.BuilderArrayPayload; import org.jf.dexlib2.builder.instruction.BuilderInstruction10t; import org.jf.dexlib2.builder.instruction.BuilderInstruction10x; import org.jf.dexlib2.builder.instruction.BuilderInstruction11n; import org.jf.dexlib2.builder.instruction.BuilderInstruction11x; import org.jf.dexlib2.builder.instruction.BuilderInstruction12x; import org.jf.dexlib2.builder.instruction.BuilderInstruction20bc; import org.jf.dexlib2.builder.instruction.BuilderInstruction20t; import org.jf.dexlib2.builder.instruction.BuilderInstruction21c; import org.jf.dexlib2.builder.instruction.BuilderInstruction21ih; import org.jf.dexlib2.builder.instruction.BuilderInstruction21lh; import org.jf.dexlib2.builder.instruction.BuilderInstruction21s; import org.jf.dexlib2.builder.instruction.BuilderInstruction21t; import org.jf.dexlib2.builder.instruction.BuilderInstruction22b; import org.jf.dexlib2.builder.instruction.BuilderInstruction22c; import org.jf.dexlib2.builder.instruction.BuilderInstruction22s; import org.jf.dexlib2.builder.instruction.BuilderInstruction22t; import org.jf.dexlib2.builder.instruction.BuilderInstruction22x; import org.jf.dexlib2.builder.instruction.BuilderInstruction23x; import org.jf.dexlib2.builder.instruction.BuilderInstruction30t; import org.jf.dexlib2.builder.instruction.BuilderInstruction31c; import org.jf.dexlib2.builder.instruction.BuilderInstruction31i; import org.jf.dexlib2.builder.instruction.BuilderInstruction31t; import org.jf.dexlib2.builder.instruction.BuilderInstruction32x; import org.jf.dexlib2.builder.instruction.BuilderInstruction35c; import org.jf.dexlib2.builder.instruction.BuilderInstruction3rc; import org.jf.dexlib2.builder.instruction.BuilderInstruction51l; import org.jf.dexlib2.builder.instruction.BuilderPackedSwitchPayload; import org.jf.dexlib2.builder.instruction.BuilderSparseSwitchPayload; import org.jf.dexlib2.iface.ExceptionHandler; import org.jf.dexlib2.iface.MethodImplementation; import org.jf.dexlib2.iface.TryBlock; import org.jf.dexlib2.iface.debug.DebugItem; import org.jf.dexlib2.iface.debug.EndLocal; import org.jf.dexlib2.iface.debug.LineNumber; import org.jf.dexlib2.iface.debug.RestartLocal; import org.jf.dexlib2.iface.debug.SetSourceFile; import org.jf.dexlib2.iface.debug.StartLocal; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.SwitchElement; import org.jf.dexlib2.iface.instruction.formats.ArrayPayload; import org.jf.dexlib2.iface.instruction.formats.Instruction10t; import org.jf.dexlib2.iface.instruction.formats.Instruction10x; import org.jf.dexlib2.iface.instruction.formats.Instruction11n; import org.jf.dexlib2.iface.instruction.formats.Instruction11x; import org.jf.dexlib2.iface.instruction.formats.Instruction12x; import org.jf.dexlib2.iface.instruction.formats.Instruction20bc; import org.jf.dexlib2.iface.instruction.formats.Instruction20t; import org.jf.dexlib2.iface.instruction.formats.Instruction21c; import org.jf.dexlib2.iface.instruction.formats.Instruction21ih; import org.jf.dexlib2.iface.instruction.formats.Instruction21lh; import org.jf.dexlib2.iface.instruction.formats.Instruction21s; import org.jf.dexlib2.iface.instruction.formats.Instruction21t; import org.jf.dexlib2.iface.instruction.formats.Instruction22b; import org.jf.dexlib2.iface.instruction.formats.Instruction22c; import org.jf.dexlib2.iface.instruction.formats.Instruction22s; import org.jf.dexlib2.iface.instruction.formats.Instruction22t; import org.jf.dexlib2.iface.instruction.formats.Instruction22x; import org.jf.dexlib2.iface.instruction.formats.Instruction23x; import org.jf.dexlib2.iface.instruction.formats.Instruction30t; import org.jf.dexlib2.iface.instruction.formats.Instruction31c; import org.jf.dexlib2.iface.instruction.formats.Instruction31i; import org.jf.dexlib2.iface.instruction.formats.Instruction31t; import org.jf.dexlib2.iface.instruction.formats.Instruction32x; import org.jf.dexlib2.iface.instruction.formats.Instruction35c; import org.jf.dexlib2.iface.instruction.formats.Instruction3rc; import org.jf.dexlib2.iface.instruction.formats.Instruction51l; import org.jf.dexlib2.iface.instruction.formats.PackedSwitchPayload; import org.jf.dexlib2.iface.instruction.formats.SparseSwitchPayload; import org.jf.dexlib2.iface.reference.Reference; import org.jf.dexlib2.iface.reference.StringReference; import org.jf.dexlib2.iface.reference.TypeReference; import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.util.ExceptionWithContext; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Created by tangyinsheng on 2017/02/16. */ public class BuilderMutableMethodImplementation implements MethodImplementation { private final DexBuilder dexBuilder; private final int registerCount; private final ArrayList instructionList = Lists.newArrayList(new MethodLocation(null, 0, 0)); private final ArrayList tryBlocks = Lists.newArrayList(); private boolean fixInstructions = true; public BuilderMutableMethodImplementation(DexBuilder dexBuilder, @Nonnull MethodImplementation methodImplementation) { this.dexBuilder = dexBuilder; this.registerCount = methodImplementation.getRegisterCount(); int codeAddress = 0; int index = 0; for (Instruction instruction : methodImplementation.getInstructions()) { codeAddress += instruction.getCodeUnits(); index++; instructionList.add(new MethodLocation(null, codeAddress, index)); } final int[] codeAddressToIndex = new int[codeAddress + 1]; Arrays.fill(codeAddressToIndex, -1); for (int i = 0; i < instructionList.size(); i++) { codeAddressToIndex[instructionList.get(i).codeAddress] = i; } List switchPayloadTasks = Lists.newArrayList(); index = 0; for (final Instruction instruction : methodImplementation.getInstructions()) { final MethodLocation location = instructionList.get(index); final Opcode opcode = instruction.getOpcode(); if (opcode == Opcode.PACKED_SWITCH_PAYLOAD || opcode == Opcode.SPARSE_SWITCH_PAYLOAD) { switchPayloadTasks.add(new Task() { @Override public void perform() { convertAndSetInstruction(location, codeAddressToIndex, instruction); } }); } else { convertAndSetInstruction(location, codeAddressToIndex, instruction); } index++; } // the switch payload instructions must be converted last, so that any switch statements that refer to them // have created the referring labels that we look for for (Task switchPayloadTask : switchPayloadTasks) { switchPayloadTask.perform(); } for (DebugItem debugItem : methodImplementation.getDebugItems()) { int debugCodeAddress = debugItem.getCodeAddress(); int locationIndex = mapCodeAddressToIndex(codeAddressToIndex, debugCodeAddress); MethodLocation debugLocation = instructionList.get(locationIndex); BuilderDebugItem builderDebugItem = convertDebugItem(debugItem); debugLocation.getDebugItems().add(builderDebugItem); builderDebugItem.location = debugLocation; } for (TryBlock tryBlock : methodImplementation.getTryBlocks()) { Label startLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress()); Label endLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress() + tryBlock.getCodeUnitCount()); for (ExceptionHandler exceptionHandler : tryBlock.getExceptionHandlers()) { tryBlocks.add(new BuilderTryBlock(startLabel, endLabel, (TypeReference) convertReference(exceptionHandler.getExceptionTypeReference()), newLabel(codeAddressToIndex, exceptionHandler.getHandlerCodeAddress()))); } } } private interface Task { void perform(); } public BuilderMutableMethodImplementation(DexBuilder dexBuilder, int registerCount) { this.dexBuilder = dexBuilder; this.registerCount = registerCount; } @Override public int getRegisterCount() { return registerCount; } @Nonnull public List getInstructions() { if (fixInstructions) { fixInstructions(); } return new AbstractList() { @Override public BuilderInstruction get(int i) { if (i >= size()) { throw new IndexOutOfBoundsException(); } if (fixInstructions) { fixInstructions(); } return instructionList.get(i).instruction; } @Override public int size() { if (fixInstructions) { fixInstructions(); } // don't include the last MethodLocation, which always has a null instruction return instructionList.size() - 1; } }; } @Nonnull @Override public List getTryBlocks() { if (fixInstructions) { fixInstructions(); } return Collections.unmodifiableList(tryBlocks); } @Nonnull @Override public Iterable getDebugItems() { if (fixInstructions) { fixInstructions(); } return Iterables.concat( Iterables.transform(instructionList, new Function>() { @Nullable @Override public Iterable apply(@Nullable MethodLocation input) { assert input != null; if (fixInstructions) { throw new IllegalStateException("This iterator was invalidated by a change to" + " this MutableMethodImplementation."); } return input.getDebugItems(); } })); } public void addCatch(@Nullable TypeReference type, @Nonnull Label from, @Nonnull Label to, @Nonnull Label handler) { tryBlocks.add(new BuilderTryBlock(from, to, type, handler)); } public void addCatch(@Nullable String type, @Nonnull Label from, @Nonnull Label to, @Nonnull Label handler) { tryBlocks.add(new BuilderTryBlock(from, to, type, handler)); } public void addCatch(@Nonnull Label from, @Nonnull Label to, @Nonnull Label handler) { tryBlocks.add(new BuilderTryBlock(from, to, handler)); } public void addInstruction(int index, BuilderInstruction instruction) { // the end check here is intentially >= rather than >, because the list always includes an "empty" // (null instruction) MethodLocation at the end. To add an instruction to the end of the list, the user would // provide the index of this empty item, which would be size() - 1. if (index >= instructionList.size()) { throw new IndexOutOfBoundsException(); } if (index == instructionList.size() - 1) { addInstruction(instruction); return; } int codeAddress = instructionList.get(index).getCodeAddress(); MethodLocation newLoc = new MethodLocation(instruction, codeAddress, index); instructionList.add(index, newLoc); instruction.location = newLoc; codeAddress += instruction.getCodeUnits(); for (int i = index + 1; i < instructionList.size(); i++) { MethodLocation location = instructionList.get(i); location.index++; location.codeAddress = codeAddress; if (location.instruction != null) { codeAddress += location.instruction.getCodeUnits(); } else { // only the last MethodLocation should have a null instruction assert i == instructionList.size() - 1; } } this.fixInstructions = true; } public void addInstruction(@Nonnull BuilderInstruction instruction) { MethodLocation last = instructionList.get(instructionList.size() - 1); last.instruction = instruction; instruction.location = last; int nextCodeAddress = last.codeAddress + instruction.getCodeUnits(); instructionList.add(new MethodLocation(null, nextCodeAddress, instructionList.size())); this.fixInstructions = true; } public void replaceInstruction(int index, @Nonnull BuilderInstruction replacementInstruction) { if (index >= instructionList.size() - 1) { throw new IndexOutOfBoundsException(); } MethodLocation replaceLocation = instructionList.get(index); replacementInstruction.location = replaceLocation; BuilderInstruction old = replaceLocation.instruction; assert old != null; old.location = null; replaceLocation.instruction = replacementInstruction; // TODO: factor out index/address fix up loop int codeAddress = replaceLocation.codeAddress + replaceLocation.instruction.getCodeUnits(); for (int i = index + 1; i < instructionList.size(); i++) { MethodLocation location = instructionList.get(i); location.codeAddress = codeAddress; Instruction instruction = location.getInstruction(); if (instruction != null) { codeAddress += instruction.getCodeUnits(); } else { assert i == instructionList.size() - 1; } } this.fixInstructions = true; } public void removeInstruction(int index) { if (index >= instructionList.size() - 1) { throw new IndexOutOfBoundsException(); } MethodLocation toRemove = instructionList.get(index); toRemove.instruction = null; MethodLocation next = instructionList.get(index + 1); toRemove.mergeInto(next); instructionList.remove(index); int codeAddress = toRemove.codeAddress; for (int i = index; i < instructionList.size(); i++) { MethodLocation location = instructionList.get(i); location.index = i; location.codeAddress = codeAddress; Instruction instruction = location.getInstruction(); if (instruction != null) { codeAddress += instruction.getCodeUnits(); } else { assert i == instructionList.size() - 1; } } this.fixInstructions = true; } public void swapInstructions(int index1, int index2) { if (index1 >= instructionList.size() - 1 || index2 >= instructionList.size() - 1) { throw new IndexOutOfBoundsException(); } MethodLocation first = instructionList.get(index1); MethodLocation second = instructionList.get(index2); // only the last MethodLocation may have a null instruction assert first.instruction != null; assert second.instruction != null; first.instruction.location = second; second.instruction.location = first; { BuilderInstruction tmp = second.instruction; second.instruction = first.instruction; first.instruction = tmp; } if (index2 < index1) { int tmp = index2; index2 = index1; index1 = tmp; } int codeAddress = first.codeAddress + first.instruction.getCodeUnits(); for (int i = index1 + 1; i <= index2; i++) { MethodLocation location = instructionList.get(i); location.codeAddress = codeAddress; Instruction instruction = location.instruction; assert instruction != null; codeAddress += location.instruction.getCodeUnits(); } this.fixInstructions = true; } @Nullable private BuilderInstruction getFirstNonNop(int startIndex) { for (int i = startIndex; i < instructionList.size() - 1; i++) { BuilderInstruction instruction = instructionList.get(i).instruction; assert instruction != null; if (instruction.getOpcode() != Opcode.NOP) { return instruction; } } return null; } private void fixInstructions() { HashSet payloadLocations = Sets.newHashSet(); for (MethodLocation location : instructionList) { BuilderInstruction instruction = location.instruction; if (instruction != null) { switch (instruction.getOpcode()) { case SPARSE_SWITCH: case PACKED_SWITCH: { MethodLocation targetLocation = ((BuilderOffsetInstruction) instruction).getTarget().getLocation(); BuilderInstruction targetInstruction = targetLocation.instruction; if (targetInstruction == null) { throw new IllegalStateException(String.format("Switch instruction at address/index " + "0x%x/%d points to the end of the method.", location.codeAddress, location.index)); } if (targetInstruction.getOpcode() == Opcode.NOP) { targetInstruction = getFirstNonNop(targetLocation.index + 1); } if (targetInstruction == null || !(targetInstruction instanceof BuilderSwitchPayload)) { throw new IllegalStateException(String.format("Switch instruction at address/index " + "0x%x/%d does not refer to a payload instruction.", location.codeAddress, location.index)); } if ((instruction.opcode == Opcode.PACKED_SWITCH && targetInstruction.getOpcode() != Opcode.PACKED_SWITCH_PAYLOAD) || (instruction.opcode == Opcode.SPARSE_SWITCH && targetInstruction.getOpcode() != Opcode.SPARSE_SWITCH_PAYLOAD)) { throw new IllegalStateException(String.format("Switch instruction at address/index " + "0x%x/%d refers to the wrong type of payload instruction.", location.codeAddress, location.index)); } if (!payloadLocations.add(targetLocation)) { throw new IllegalStateException("Multiple switch instructions refer to the same payload. " + "This is not currently supported. Please file a bug :)"); } ((BuilderSwitchPayload) targetInstruction).referrer = location; break; } default: { break; } } } } boolean madeChanges; do { madeChanges = false; for (int index = 0; index < instructionList.size(); index++) { MethodLocation location = instructionList.get(index); BuilderInstruction instruction = location.instruction; if (instruction != null) { switch (instruction.getOpcode()) { case GOTO: { int offset = ((BuilderOffsetInstruction) instruction).internalGetCodeOffset(); if (offset < Byte.MIN_VALUE || offset > Byte.MAX_VALUE) { BuilderOffsetInstruction replacement; if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { replacement = new BuilderInstruction30t(Opcode.GOTO_32, ((BuilderOffsetInstruction) instruction).getTarget()); } else { replacement = new BuilderInstruction20t(Opcode.GOTO_16, ((BuilderOffsetInstruction) instruction).getTarget()); } replaceInstruction(location.index, replacement); madeChanges = true; } break; } case GOTO_16: { int offset = ((BuilderOffsetInstruction) instruction).internalGetCodeOffset(); if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { BuilderOffsetInstruction replacement = new BuilderInstruction30t(Opcode.GOTO_32, ((BuilderOffsetInstruction) instruction).getTarget()); replaceInstruction(location.index, replacement); madeChanges = true; } break; } case SPARSE_SWITCH_PAYLOAD: case PACKED_SWITCH_PAYLOAD: if (((BuilderSwitchPayload) instruction).referrer == null) { // if the switch payload isn't referenced, just remove it removeInstruction(index); index--; madeChanges = true; break; } // intentional fall-through case ARRAY_PAYLOAD: { if ((location.codeAddress & 0x01) != 0) { int previousIndex = location.index - 1; MethodLocation previousLocation = instructionList.get(previousIndex); Instruction previousInstruction = previousLocation.instruction; assert previousInstruction != null; if (previousInstruction.getOpcode() == Opcode.NOP) { removeInstruction(previousIndex); index--; } else { addInstruction(location.index, new BuilderInstruction10x(Opcode.NOP)); index++; } madeChanges = true; } break; } default: { break; } } } } } while (madeChanges); fixInstructions = false; } private int mapCodeAddressToIndex(@Nonnull int[] codeAddressToIndex, int codeAddress) { int index; do { index = codeAddressToIndex[codeAddress]; if (index < 0) { codeAddress--; } else { return index; } } while (true); } private int mapCodeAddressToIndex(int codeAddress) { float avgCodeUnitsPerInstruction = 1.9f; int index = (int) (codeAddress / avgCodeUnitsPerInstruction); if (index >= instructionList.size()) { index = instructionList.size() - 1; } MethodLocation guessedLocation = instructionList.get(index); if (guessedLocation.codeAddress == codeAddress) { return index; } else if (guessedLocation.codeAddress > codeAddress) { do { index--; } while (instructionList.get(index).codeAddress > codeAddress); return index; } else { do { index++; } while (index < instructionList.size() && instructionList.get(index).codeAddress <= codeAddress); return index - 1; } } @Nonnull public Label newLabelForAddress(int codeAddress) { if (codeAddress < 0 || codeAddress > instructionList.get(instructionList.size() - 1).codeAddress) { throw new IndexOutOfBoundsException(String.format("codeAddress %d out of bounds", codeAddress)); } MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddress)); return referent.addNewLabel(); } @Nonnull public Label newLabelForIndex(int instructionIndex) { if (instructionIndex < 0 || instructionIndex >= instructionList.size()) { throw new IndexOutOfBoundsException(String.format("instruction index %d out of bounds", instructionIndex)); } MethodLocation referent = instructionList.get(instructionIndex); return referent.addNewLabel(); } @Nonnull private Label newLabel(@Nonnull int[] codeAddressToIndex, int codeAddress) { MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress)); return referent.addNewLabel(); } private static class SwitchPayloadReferenceLabel extends Label { @Nonnull public MethodLocation switchLocation; } @Nonnull public Label newSwitchPayloadReferenceLabel(@Nonnull MethodLocation switchLocation, @Nonnull int[] codeAddressToIndex, int codeAddress) { MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress)); SwitchPayloadReferenceLabel label = new SwitchPayloadReferenceLabel(); label.switchLocation = switchLocation; referent.getLabels().add(label); return label; } private void setInstruction(@Nonnull MethodLocation location, @Nonnull BuilderInstruction instruction) { location.instruction = instruction; instruction.location = location; } private void convertAndSetInstruction(@Nonnull MethodLocation location, int[] codeAddressToIndex, @Nonnull Instruction instruction) { switch (instruction.getOpcode().format) { case Format10t: setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex, (Instruction10t) instruction)); return; case Format10x: setInstruction(location, newBuilderInstruction10x((Instruction10x) instruction)); return; case Format11n: setInstruction(location, newBuilderInstruction11n((Instruction11n) instruction)); return; case Format11x: setInstruction(location, newBuilderInstruction11x((Instruction11x) instruction)); return; case Format12x: setInstruction(location, newBuilderInstruction12x((Instruction12x) instruction)); return; case Format20bc: setInstruction(location, newBuilderInstruction20bc((Instruction20bc) instruction)); return; case Format20t: setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex, (Instruction20t) instruction)); return; case Format21c: setInstruction(location, newBuilderInstruction21c((Instruction21c) instruction)); return; case Format21ih: setInstruction(location, newBuilderInstruction21ih((Instruction21ih) instruction)); return; case Format21lh: setInstruction(location, newBuilderInstruction21lh((Instruction21lh) instruction)); return; case Format21s: setInstruction(location, newBuilderInstruction21s((Instruction21s) instruction)); return; case Format21t: setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex, (Instruction21t) instruction)); return; case Format22b: setInstruction(location, newBuilderInstruction22b((Instruction22b) instruction)); return; case Format22c: setInstruction(location, newBuilderInstruction22c((Instruction22c) instruction)); return; case Format22s: setInstruction(location, newBuilderInstruction22s((Instruction22s) instruction)); return; case Format22t: setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex, (Instruction22t) instruction)); return; case Format22x: setInstruction(location, newBuilderInstruction22x((Instruction22x) instruction)); return; case Format23x: setInstruction(location, newBuilderInstruction23x((Instruction23x) instruction)); return; case Format30t: setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex, (Instruction30t) instruction)); return; case Format31c: setInstruction(location, newBuilderInstruction31c((Instruction31c) instruction)); return; case Format31i: setInstruction(location, newBuilderInstruction31i((Instruction31i) instruction)); return; case Format31t: setInstruction(location, newBuilderInstruction31t(location, codeAddressToIndex, (Instruction31t) instruction)); return; case Format32x: setInstruction(location, newBuilderInstruction32x((Instruction32x) instruction)); return; case Format35c: setInstruction(location, newBuilderInstruction35c((Instruction35c) instruction)); return; case Format3rc: setInstruction(location, newBuilderInstruction3rc((Instruction3rc) instruction)); return; case Format51l: setInstruction(location, newBuilderInstruction51l((Instruction51l) instruction)); return; case PackedSwitchPayload: setInstruction(location, newBuilderPackedSwitchPayload(location, codeAddressToIndex, (PackedSwitchPayload) instruction)); return; case SparseSwitchPayload: setInstruction(location, newBuilderSparseSwitchPayload(location, codeAddressToIndex, (SparseSwitchPayload) instruction)); return; case ArrayPayload: setInstruction(location, newBuilderArrayPayload((ArrayPayload) instruction)); return; default: throw new ExceptionWithContext("Instruction format %s not supported", instruction.getOpcode().format); } } @Nonnull private BuilderInstruction10t newBuilderInstruction10t(int codeAddress, int[] codeAddressToIndex, @Nonnull Instruction10t instruction) { return new BuilderInstruction10t( instruction.getOpcode(), newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); } @Nonnull private BuilderInstruction10x newBuilderInstruction10x(@Nonnull Instruction10x instruction) { return new BuilderInstruction10x( instruction.getOpcode()); } @Nonnull private BuilderInstruction11n newBuilderInstruction11n(@Nonnull Instruction11n instruction) { return new BuilderInstruction11n( instruction.getOpcode(), instruction.getRegisterA(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction11x newBuilderInstruction11x(@Nonnull Instruction11x instruction) { return new BuilderInstruction11x( instruction.getOpcode(), instruction.getRegisterA()); } @Nonnull private BuilderInstruction12x newBuilderInstruction12x(@Nonnull Instruction12x instruction) { return new BuilderInstruction12x( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB()); } @Nonnull private BuilderInstruction20bc newBuilderInstruction20bc(@Nonnull Instruction20bc instruction) { return new BuilderInstruction20bc( instruction.getOpcode(), instruction.getVerificationError(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction20t newBuilderInstruction20t(int codeAddress, int[] codeAddressToIndex, @Nonnull Instruction20t instruction) { return new BuilderInstruction20t( instruction.getOpcode(), newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); } @Nonnull private BuilderInstruction21c newBuilderInstruction21c(@Nonnull Instruction21c instruction) { return new BuilderInstruction21c( instruction.getOpcode(), instruction.getRegisterA(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction21ih newBuilderInstruction21ih(@Nonnull Instruction21ih instruction) { return new BuilderInstruction21ih( instruction.getOpcode(), instruction.getRegisterA(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction21lh newBuilderInstruction21lh(@Nonnull Instruction21lh instruction) { return new BuilderInstruction21lh( instruction.getOpcode(), instruction.getRegisterA(), instruction.getWideLiteral()); } @Nonnull private BuilderInstruction21s newBuilderInstruction21s(@Nonnull Instruction21s instruction) { return new BuilderInstruction21s( instruction.getOpcode(), instruction.getRegisterA(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction21t newBuilderInstruction21t(int codeAddress, int[] codeAddressToIndex, @Nonnull Instruction21t instruction) { return new BuilderInstruction21t( instruction.getOpcode(), instruction.getRegisterA(), newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); } @Nonnull private BuilderInstruction22b newBuilderInstruction22b(@Nonnull Instruction22b instruction) { return new BuilderInstruction22b( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction22c newBuilderInstruction22c(@Nonnull Instruction22c instruction) { return new BuilderInstruction22c( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction22s newBuilderInstruction22s(@Nonnull Instruction22s instruction) { return new BuilderInstruction22s( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction22t newBuilderInstruction22t(int codeAddress, int[] codeAddressToIndex, @Nonnull Instruction22t instruction) { return new BuilderInstruction22t( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB(), newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); } @Nonnull private BuilderInstruction22x newBuilderInstruction22x(@Nonnull Instruction22x instruction) { return new BuilderInstruction22x( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB()); } @Nonnull private BuilderInstruction23x newBuilderInstruction23x(@Nonnull Instruction23x instruction) { return new BuilderInstruction23x( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB(), instruction.getRegisterC()); } @Nonnull private BuilderInstruction30t newBuilderInstruction30t(int codeAddress, int[] codeAddressToIndex, @Nonnull Instruction30t instruction) { return new BuilderInstruction30t( instruction.getOpcode(), newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset())); } @Nonnull private BuilderInstruction31c newBuilderInstruction31c(@Nonnull Instruction31c instruction) { return new BuilderInstruction31c( instruction.getOpcode(), instruction.getRegisterA(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction31i newBuilderInstruction31i(@Nonnull Instruction31i instruction) { return new BuilderInstruction31i( instruction.getOpcode(), instruction.getRegisterA(), instruction.getNarrowLiteral()); } @Nonnull private BuilderInstruction31t newBuilderInstruction31t(@Nonnull MethodLocation location, int[] codeAddressToIndex, @Nonnull Instruction31t instruction) { int codeAddress = location.getCodeAddress(); Label newLabel; if (instruction.getOpcode() != Opcode.FILL_ARRAY_DATA) { // if it's a sparse switch or packed switch newLabel = newSwitchPayloadReferenceLabel(location, codeAddressToIndex, codeAddress + instruction.getCodeOffset()); } else { newLabel = newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()); } return new BuilderInstruction31t( instruction.getOpcode(), instruction.getRegisterA(), newLabel); } @Nonnull private BuilderInstruction32x newBuilderInstruction32x(@Nonnull Instruction32x instruction) { return new BuilderInstruction32x( instruction.getOpcode(), instruction.getRegisterA(), instruction.getRegisterB()); } @Nonnull private BuilderInstruction35c newBuilderInstruction35c(@Nonnull Instruction35c instruction) { return new BuilderInstruction35c( instruction.getOpcode(), instruction.getRegisterCount(), instruction.getRegisterC(), instruction.getRegisterD(), instruction.getRegisterE(), instruction.getRegisterF(), instruction.getRegisterG(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) { return new BuilderInstruction3rc( instruction.getOpcode(), instruction.getStartRegister(), instruction.getRegisterCount(), convertReference(instruction.getReference())); } @Nonnull private BuilderInstruction51l newBuilderInstruction51l(@Nonnull Instruction51l instruction) { return new BuilderInstruction51l( instruction.getOpcode(), instruction.getRegisterA(), instruction.getWideLiteral()); } @Nullable private MethodLocation findSwitchForPayload(@Nonnull MethodLocation payloadLocation) { MethodLocation location = payloadLocation; MethodLocation switchLocation = null; do { for (Label label : location.getLabels()) { if (label instanceof SwitchPayloadReferenceLabel) { if (switchLocation != null) { throw new IllegalStateException("Multiple switch instructions refer to the same payload. " + "This is not currently supported. Please file a bug :)"); } switchLocation = ((SwitchPayloadReferenceLabel) label).switchLocation; } } // A switch instruction can refer to the payload instruction itself, or to a nop before the payload // instruction. // We need to search for all occurrences of a switch reference, so we can detect when multiple switch // statements refer to the same payload // TODO: confirm that it could refer to the first NOP in a series of NOPs preceding the payload if (location.index == 0) { return switchLocation; } location = instructionList.get(location.index - 1); if (location.instruction == null || location.instruction.getOpcode() != Opcode.NOP) { return switchLocation; } } while (true); } @Nonnull private BuilderPackedSwitchPayload newBuilderPackedSwitchPayload(@Nonnull MethodLocation location, @Nonnull int[] codeAddressToIndex, @Nonnull PackedSwitchPayload instruction) { List switchElements = instruction.getSwitchElements(); if (switchElements.size() == 0) { return new BuilderPackedSwitchPayload(0, null); } MethodLocation switchLocation = findSwitchForPayload(location); int baseAddress; if (switchLocation == null) { baseAddress = 0; } else { baseAddress = switchLocation.codeAddress; } List